I think something like this was asked before but I'm having a hard time locating it.
Say, I want to match against structures which have various optional parts. In simplified form:
macro_rules! foo {
($(&)? $(::)? $($t:ident)::+) => { ... }
}
Now, in the macro body, I want to expand out &
or ::
only if it appeared in the pattern. The only way I know to handle this is. I don't know how to capture the &
or ::
in meta variables to capture them and my workaround is to write out all the variants:
macro_rules! foo {
(& :: $($t:ident)::+) => { ... };
( :: $($t:ident)::+) => { ... };
(& $($t:ident)::+) => { ... };
( $($t:ident)::+) => { ... };
}
I know I can do this straightforwardly with proc_macro
s, but I wanted to see if there were any way these days to do it with declarative macros.
And in case folks try to suggest I match the whole thing against an expr
or something, I actually need the various parts of the matched construct to produce different types of things (path
, pattern
) in the macro body.
Only by matching on some inner "markers". In your case:
// since `&` and `::` are `:punct:`
// and `$path` starts with an `:ident`
// `:literal` can be a viable option,
// while `:punct` and `:ident` can't
macro_rules! foo {
(
$(& $($_amp:literal)?)? // ampersand
$(:: $($_cs:literal)?)? // colons
$($seg:ident)::+ // path segments
) => {
println!("{}", concat![
stringify![ $(& $($_amp)?)? ], // &
stringify![ $(:: $($_cs)?)? ], // ::
stringify![ $($seg)::+ ],
])
}
}
2 Likes
Interesting idea, thanks for suggesting! I imagine it might result in a confusing error in a later part of the expansion if the caller does put a literal there.
But that's unlikely in my use-case here, thanks!
Well, there is a way around that problem too:
macro_rules! foo {
(
$(& $($_amp:literal)?)? // ampersand
$(:: $($_cs:literal)?)? // colons
$($seg:ident)::+ // path segments
) => {
...
// compile-time check, to make sure
// no *actual* literals were passed in
foo!(_no_lit: $($($_amp)?)? $($($_cs)?)?);
};
(_no_lit: ) => {};
(_no_lit: $($lit:literal)+) => {
// set the rules/syntax/expectations here
compile_error!("invalid macro call; not a `(&)(::)[$path::]+");
};
}
1 Like
Very cool! This is a significant reduction in duplication for me! I imagine this overall trick can't be used to optionally capture the ref
before an expr
given that expr
can start with pretty much anything? (Even lifetimes can start an expr
as loop labels.)
macro_rules! foo {
($(ref)? $($e:expr)::+) => { ... }
}
Or do you have some wizardry to offer for this one as well?
You can't use that overall trick there. But you can use another one:
macro_rules! foo {
// the `virtual` part there is needed to disambiguate
// the marker `$_ref:block` from any actual `$e` { block } expressions;
($(ref $(virtual $_ref:block)?)? $e:expr) => {{
// assuming you want to $(maybe)? bind by `ref`
let $(ref $($_ref)?)? bind = $e;
// replace this with what you need
use std::any::type_name_of_val as type_of;
println!("type of `bind` is: {}", type_of(&bind));
}}
}
fn bar() {
foo!(5); // prints "i32"
foo!(ref true); // "&bool"
foo!(ref { "what" }); // &&str
}
Note that in your last foo
the problem becomes the $($e:expr)::+
itself: since the ::
is a perfectly valid part of any path expression. Also, you can substitute (pun intended: "virtually") anything for the reserved virtual
keyword: __
or REF
or _NEVER_USE_THIS_IN_YOUR_OWN_CODE_PLZ
.
I just happen to like the auto-highlight. Quite a fitting choice as well, though: intuition-wise.
1 Like
Beautiful! Well done! After I did even the first refactor, I had to question the readability for one of the two macros that benefited less from the de-duplication. I do wish they would perhaps add a more native support for group capture that's more readable. Though one would have to figure out what meta-type to associate with each group for backward-compatibility considerations.
That's the biggest drawback of getting too "creative" in this regard. Either you'll need to document each and every bit of your "wizardry" incantation to make it crystal clear what in the actual world it's supposed to do - or you'll end up scrapping the whole thing sooner or later for something a lot less clever yet far more readable. To sum it up: just because you can, doesn't mean you should.