5

Github Add :value macro capture designator by CAD97 · Pull Request #3106 · rust-...

 3 years ago
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.
neoserver,ios ssh client

Copy link

CAD97 commented 20 days ago

edited

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;.

Rendered

Copy link

Author

CAD97 commented 20 days ago

(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...)

CAD97

changed the title Create 0000-macro-value

Add :value macro capture designator

20 days ago

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.

Copy link

Author

CAD97 commented 20 days ago

edited

... I'm tired, obviously. I fixed the file extension sweat_smile 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

edited

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

edited

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.)

Copy link

Author

CAD97 commented 19 days ago

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.

Copy link

ojeda left a comment •

edited

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.

0000-macro-value.md

Outdated

Show resolved

A new macro matching mode,

`$:value`,

is added.

Comment on lines

+79 to +81

ojeda 18 days ago

edited

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.

0000-macro-value.md

Outdated

Show resolved

0000-macro-value.md

Outdated

Show resolved

0000-macro-value.md

Outdated

Show resolved

# 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:

  1. Want to bypass the type system
  2. Closures are too restrictive
  3. Writing it as a function would cause lifetime issues
  4. 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

edited

Member

nit: this word is redundant -- "instead expand to [code] instead."


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK