Github Add :value macro capture designator by CAD97 · Pull Request #3106 · rust-...
source link: https://github.com/rust-lang/rfcs/pull/3106
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Add a new macro_rules matcher, $name:value
, with identical semantics to that of a function capture.
That is, it evaluates exactly once at the time of the macro call, and the macro manipulates a named temporary,
identically to how a function call works. Importantly, this extends to the lifetime of temporaries,
which is different from a macro expanding to let name = $name;
.
(You can view a rich diff in the Files Changed tab, but apparently only for edited files, not added ones. And I was excited to use a new way to make the rendered link...)
you probably wanted the file to be a .md file as opposed to what it currently is: a file with no extension.
Also, the Rendered link should probably link to a file on a git branch, not to a particular commit, since then it will not need to be changed each time you push more commits.
... I'm tired, obviously. I fixed the file extension and rendered link
But still, this moves `macro_rules!` away from just being token (tree) substitution,
and gives `macro_rules!` a superpower that isn't available to function-like proc macros.
# Alternatives
petrochenkov 19 days ago •
Contributor
Can this be implemented using a proc macro?
I think the hard constraint on the feature is that the output of mac!()
using the value
matcher must be representable as a regular token stream.
(In this sense the "which cannot be directly implemented by just a desugaring of the macro_rules!
invocation" part sounds a bit concerning.)
But if it can be represented as a token stream, then it should be implementable as a proc macro as well.
CAD97 19 days ago •
Author
What I'm trying to convey is that $:value
cannot be implemented as a macro_rules!
to macro_rules!
transformation. The match
trick has the exact semantics that we want here, so the semantics can be gotten by a simple token tree expansion (and thus by proc macros aware of the match
trick). When I say this would be something macro_rules!
has that proc macros don't, I'm specifically referring to the $:value
shorthand, not any expressive power.
As an example,
macro_rules! sum_twice { ( $( $x:value ),+ $(,)? ) => ( 0 $( + $x )* $( + $x)* ); } sum_twice( $a, $b, $c );
has no macro_rules!
way to implement the desired semantics (due to the repetition), but can potentially expanded as
match $a { x$a => match $b { x$b => match $c { x$c => 0 + x$a + x$b + x$c + x$a + x$b + x$c ,} ,} ,}
(In practice I'm not sure how reasonable actually expanding to this or some other more compact MIR form would be.)
Something I just realized: $:value
(as written) forces the macro to evaluate as a block expression. While this is the intent w.r.t. passed in temporaries, it would be nice if $:value
could also work for macros intended to be used in statement position (e.g. do not wrap their contents in another block and even potentially expand to let
statements and/or items).
For statement position, it might have the correct behavior to bind to a let
at the start of the expansion and call drop
at the end of the expansion, but I'm not well-versed enough in temporary lifetime promotion to actually know. (I don't think so, as dropping the binding doesn't even do anything if the binding is a Copy
reference.)
So while an expression-position expansion can be explained by the match
trick, it's currently impossible (as I understand it) to both get fully correct temporary behavior that matches a function call, and expand in statement position.
I think the RFC should investigate whether the proposal is equivalent to the match
trick or not, because that is the main question around it.
If this is indeed equivalent to the match
trick, then it isn't clear it is worth the complexity cost. In this case, I would suggest showing how it could simplify several real-life examples out there.
If it isn't, then the proposal becomes quite more interesting on its own! In this case, showing potential unlocked use cases (and workarounds used by projects so far) would be best.
So either finding a counterexample or a way to show they are equivalent looks key.
A new macro matching mode,
`$:value`,
is added.
Comment on lines
+79 to +81
ojeda 18 days ago •
Not sure why there are so many new lines across the source. It makes it harder to read in non-rendered mode (which partially defeats the purpose of Markdown).
shepmaster 18 days ago
Member
A pattern I've seen is to use newlines after punctuation. The reasoning there is to reduce the diff noise of subsequent changes, as English prose often changes inside a clause, but less frequently between it. Using "arbitrary" newlines (e.g. at 80 columns) leads to a cascading series of diffs if you add enough words to an early line.
ojeda 18 days ago
A smarter diff for Markdown in GitHub would be useful :)
CAD97 18 days ago
Author
Yeah, and actually the rich diff does pretty okay at it. I probably did too much newlines in this document, but there's a happy medium somewhere. In any case, completely unwrapped text is worse than overly wrapped (in my opinion), and maintaining "reasonable" wrapping in a consistently updated document is annoying.
It also helps me pay attention to my clause length. If I'm just writing without thinking about it, I tend to get super long sentences, with (potentially many) levels of indirection, and it becomes difficult to read -- so I have ended up with some (kludgy but workable) workflow things to help balance it.[1]
[1]: yes this mess of a run-on was on purpose to illustrate the point
fn c() { let _c = mfn (&Wrap("c")); } // compiles
```
plus there are other differences with drop timing of temporaries,
ojeda 18 days ago
It would be best to explain those differences.
CAD97 18 days ago
Author
I know and.... I'm not perfectly aware of what they all are, thus the sidestep here.
# Summary
[summary]: #summary
Add a new macro_rules matcher, `$name:value`, with identical semantics to that of a function capture.
ojeda 18 days ago
Is "function capture" accurate?
# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation
(in a section explaining `macro_rules` matchers:)
ojeda 18 days ago
Spurious comment?
CAD97 18 days ago
Author
This may or may not be useful, but it is intended, to indicate the context of when a guide/learner would be expected to explain/learn about $:value
.
and each expansion of the capture refers to the same value,
just like function arguments.
(no need to mention temporary lifetimes in the guide.)
ojeda 18 days ago
Spurious comment?
CAD97 18 days ago
Author
This is less rationalized than the previous, but still intentional. It's basically just an explicit acknowledgement that one of the main motivators of the feature isn't even mentioned in the guide explanation.
This is basically the exact feature that "`macro_rules!` with function-like captures" is trying to serve,
except for one important thing: a `macro fn` is (potentially) still a `fn` in that it has one fixed airity,
and can't be overloaded like a macro can.
Basically, `macro fn` is asking for "macros 2.0,"
CAD97 18 days ago
Author
This comma is part of
asking for macros 2.0, which is still desirable, but still
so isn't it required here? (Does British English put the comma outside the quotes? I know the American English rules put the comma inside the quotes (and the Rust project generally prefers American English).)
joshtriplett 13 days ago
Member
@CAD97 In technical writing, it's entirely normal to always put punctuation outside the quotes if it's not part of what's being quoted, following the British English quoting style even in otherwise American English writing.
Kotlin's [`inline fn`](https://kotlinlang.org/docs/inline-functions.html) behave similarly to (typechecked) macros.
Semantically, `inline fn` is inlined into the call site,
allowing things like `return`/`break`/`continue`ing from the calling scope.
Otherwise, `inline fn` behaves semantically like a function call.
Comment on lines
+258 to +261
Aloso 18 days ago
I believe this is not quite accurate: You can't return
/break
/continue
in an inline function from the calling scope, this only applies to closures passed to an inline function. I was told that Swift has the same feature.
Regarding prior art: Macros are usually used instead of functions when one of the following applies:
- Want to bypass the type system
- Closures are too restrictive
- Writing it as a function would cause lifetime issues
- Desire for more expressive syntax, like variadic, named or optional arguments
Kotlin's inline functions solve problem 2. Dynamically typed languages (and languages with more powerful type inference, like OCaml) solve problem 1. Many languages also have fancy syntax such as variadics or template strings, to reduce problem 4. Problem 3 is specific to Rust. I believe my point is that there is no prior art because most languages get by without macros (and if a language has a macro system it's rarely as advanced as Rust's).
}
```
instead.
cuviper 12 days ago •
Member
nit: this word is redundant -- "instead expand to [code] instead."
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK