Why does a trailing comma prevent this macro from compiling? - rust

I am trying to learn macros, to that effect I made this example:
macro_rules! Reflect
{
($name:ident {$($field_name:ident : $field_type:ty), *}) =>
{
struct $name
{
$($field_name : $field_type), *
}
};
}
Reflect!(
Test
{
field : usize,
field2 : usize,
}
);
This exact syntax generates:
|
183 | macro_rules! Reflect
| -------------------- when calling this macro
...
202 | }
| ^ no rules expected this token in macro call
But if I take out the last coma after field2 I can compile no problem. That makes no sense to me, the matching pattern has a comma at the end, why is it not always looking for a trailing comma?

The way to write a macro which allows a trailing comma, but does not require it, is:
$($field_name:ident : $field_type:ty),* $(,)?
That is: a comma-separated repetition, followed by an optional comma.

Apparently one must do this instead:
macro_rules! Reflect
{
($name:ident {$($field_name:ident : $field_type:ty,) *}) =>
{
struct $name
{
$($field_name : $field_type), *
}
};
}
Notice the comma moved.

Related

How to match a struct instantiation with macro_rules

Since this took me a while to figure out, I may as well share how I fixed it.
I was trying to wrap every item on a struct with some function, in my case Arc::new(Mutex::new(item)) with macro_rules
My initial attempt was many variations on this:
macro_rules! decl_sr {
(
$name:ident {
$( $it:ident : $value:expr) ,*
}
) => {
$name {
$( $it: Arc::new(Mutex::new( $value )) ),*
}
};
}
And the idea was to use it like this:
let mut value = decl_sr!{
StructName {
field_1: Value1::from_function_call(parameter1, parameter2),
// -- snip
field_n: ValueN::from_function_call(parameter1, parameter2),
}
}
So it actually resulted in this:
let mut value = decl_sr!{
StructName {
field_1: Arc::new(Mutex::new(Value1::from_function_call(parameter1, parameter2))),
// -- snip
field_n: Arc::new(Mutex::new(ValueN::from_function_call(parameter1, parameter2))),
}
}
The correct answer was this:
macro_rules! decl_sr {
(
$name:ident {
$( $it:ident : $value:expr, )*
}
) => {
$name {
$( $it: Arc::new(Mutex::new( $value )) ),*
}
};
With the comma (',') inside the repetition pattern.
Otherwise it would throw the classic macro error no rules expected token '}'. -Z macro-backtrace and trace_macros!(true); didn't help at all.
The only thing that tipped me off was this other question, but it wasn't exactly about this.
I may have picked too loose/bad fragment specifiers, feel free to correct me/suggest better options in the comments. Most of the time trying to make this works was thinking they were the problem, not the comma so I was glad it worked at all.

Question on invoking another macro_rules in macro_rules definition

I'm implementing writing TLV packet to somewhat impl std::io::Write.
First I implement WriteBE<T> trait, whose write_be(&mut self, data: T) method can write data with type T to Self. (implementation details omitted)
And I'm trying to use macro_rules! to implement calculation of total packet length in compile time (because most packets have fixed length in my case). macros are as follows:
macro_rules! len_in_expr {
(
self.write_be( $data: expr $(,)? ) $(?)? $(;)*
) => {
std::mem::size_of_val(&$data)
};
(
write_be(self, $data: expr $(,)? ) $(?)? $(;)*
) => {
std::mem::size_of_val(&$data)
};
(
$other: expr
) => {
0
};
}
/// calculate total write size in block
macro_rules! tlv_len_in_block {
({
$( $e: expr );* $(;)?
}) => {
0 $(
+ ( len_in_expr!($e) )
)*
};
}
But when I calculating total length like this:
fn main() {
let y = tlv_len_in_block!({
write_be(self, 0u32,)?;
});
println!("y={}", y);
}
I get a result 0.
If I comment the $other: expr match arm, I get a compile error:
6 | macro_rules! len_in_expr {
| ------------------------ when calling this macro
...
30 | + ( len_in_expr!($e) )
| ^^ no rules expected this token in macro call
...
39 | let y = tlv_len_in_block!({
| _____________-
40 | | write_be(self, 0u32,)?;
41 | | });
| |______- in this macro invocation
What's the problem with my code? And how can I fix it?
Once metavariables inside macro_rules! are captured into some fragment specifier (e.g. expr), they cannot be decomposed anymore. Quoting the reference:
When forwarding a matched fragment to another macro-by-example, matchers in the second macro will see an opaque AST of the fragment type. The second macro can't use literal tokens to match the fragments in the matcher, only a fragment specifier of the same type. The ident, lifetime, and tt fragment types are an exception, and can be matched by literal tokens. The following illustrates this restriction:
macro_rules! foo {
($l:expr) => { bar!($l); }
// ERROR: ^^ no rules expected this token in macro call
}
macro_rules! bar {
(3) => {}
}
foo!(3);
The following illustrates how tokens can be directly matched after matching a tt fragment:
// compiles OK
macro_rules! foo {
($l:tt) => { bar!($l); }
}
macro_rules! bar {
(3) => {}
}
foo!(3);
Once tlv_len_in_block!() captured write_be(self, 0u32,)? inside $e, it cannot be decomposed into write_be(self, $data:expr $(,)? ) and thus, cannot be matched by the second case of the len_in_expr!()` macro, as it should have been.
There are generally two solutions to this problem:
The first is, if possible, decomposing them from the beginning. The problem is that this is not always possible. In this case, for example, I don't see a way for that to work.
The second way is much more complicated and it is using the Push-down Accumulation technique together with tt Munching.
The idea is as follows: instead of parsing the input as whole, we parse each piece one at a time. Then, recursively, we forward the parsed bits and the yet-to-parse bit to ourselves. We also should have a stop condition on an empty input.
Here is how it will look like in your example:
macro_rules! tlv_len_in_block_impl {
// Stop condition - no input left to parse.
(
parsed = [ $($parsed:tt)* ]
rest = [ ]
) => {
$($parsed)*
};
(
parsed = [ $($parsed:tt)* ]
rest = [
self.write_be( $data:expr $(,)? ) $(?)? ;
$($rest:tt)*
]
) => {
tlv_len_in_block_impl!(
parsed = [
$($parsed)*
+ std::mem::size_of_val(&$data)
]
rest = [ $($rest)* ]
)
};
(
parsed = [ $($parsed:tt)* ]
rest = [
write_be(self, $data:expr $(,)? ) $(?)? ;
$($rest:tt)*
]
) => {
tlv_len_in_block_impl!(
parsed = [
$($parsed)*
+ std::mem::size_of_val(&$data)
]
rest = [ $($rest)* ]
)
};
}
/// calculate total write size in block
macro_rules! tlv_len_in_block {
({
$($input:tt)*
}) => {
tlv_len_in_block_impl!(
parsed = [ 0 ]
rest = [ $($input)* ]
)
};
}
(Note that this is not exactly the same as your macro - mine requires a trailing semicolon, while in yours it's optional. It's possible to make it optional here, too, but it will be much more complicated.
Playground.

Why does a macro call inside another macro not expand, but instead I get "no rules expected the token `!`"?

I'm calling a macro within a macro, i.e
macro_rules! foo {
(yes) => {
true
};
() => {
false
};
}
macro_rules! baz {
() => {
[(); 0]
};
($args: tt) => {
$args
};
}
macro_rules! parse_rule {
($rule: tt, $args: tt, $newline: expr) => {
println!("The rule is {}, with args {:?}", $rule, $args);
if $newline {
println!()
}
};
}
macro_rules! bar {
($($rule: tt $([$($args: tt),*])? $($flag: ident)?);+) => {
$(parse_rule!($rule, baz!($([$($args),*])?), foo!($($flag)?)));+
}
}
fn main() {
bar!("hi" yes; "there" ["are", "some", "args"]; "no" yes);
}
The compiler complains about me calling baz within the parse_rule invocation:
error: no rules expected the token `!`
--> src/main.rs:30:33
|
19 | macro_rules! parse_rule {
| ----------------------- when calling this macro
...
30 | $(parse_rule!($rule, baz!($([$($args),*])?), foo!($($flag)?)));+
| ^ no rules expected this token in macro call
...
35 | bar!("hi" yes; "there" ["are", "some", "args"]; "no" yes);
| ---------------------------------------------------------- in this macro invocation
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
Why does it not expand?
Macros are are invoked with their unexpanded arguments. macro_call!(arg) is three token trees, not one:
macro_rules! example {
($($x:tt)+) => {
$(eprintln!(">{}<", stringify!($x));)*
}
}
fn main() {
example!(macro_call!(arg));
}
>macro_call<
>!<
>(arg)<
Your macro only allows for a single token tree ($args: tt). That matches the macro's name, leaving the !. That isn't matched by anything, so you get your error.
You probably want $args: expr.
is there a way to expand it before? I want to parse some of the elements of the array manually
There's no "more eager" expansion, that I know of. It has been suggested in various RFCs (e.g. Eager Macro Expansion — 2320).
I'd suggest reflowing your code such that parse_rule calls foo / bar itself, or baking the logic of foo / bar directly into parse_rule. Internal rules are quite common for this.
See also:
What does an # symbol mean in a Rust declarative macro?

Pass a static string to a macro_rule in rust

I wrote the following code :
macro_rules! my_macro{
("A") => {
println!("Macro called !")
}
}
fn main(){
static test: &'static str = "A";
my_macro!(test);
}
but I have the following error :
error: no rules expected the token `test`
--> test.rt:9:19
|
1 | macro_rules! my_macro{
| --------------------- when calling this macro
...
9 | my_macro!(test);
| ^^^^ no rules expected this token in macro call
error: aborting due to previous error
However, it works fine if I directly call my_macro("A"). Is it possible to fix this ?
Is it possible to fix this ?
No. Macros are expanded at compile time before item names are resolved, therefore your macro has no idea what the value of test is (and would have no idea even if it were a const rather than a static).
so the first problem here is that you macro expects a pattern of "A" not a variable that contains "A"
when you create macros you define certain patterns and follow those patterns in your case your macro must always have "A" in it but it is not a string a it is a pattern of double quote followed by capital a followed by another double quote
If you want to pass a value you should use variable syntax and define what it should expect such as ($a:expr)=>{...}
here you can see all magic tokens possible just scroll down a bit on that docs there are a lot of great examples
PS. here is a macro I use for responding from my endpoints
macro_rules! resp {
(ok) => {
|_| actix_web::HttpResponse::Ok().body(r#"{"success":true}"#)
};
(ok,$data:expr) => {
|_| actix_web::HttpResponse::Ok().json(serde_json::json!({"success":true,"data":$data}))
};
(ok,) => {
|d| actix_web::HttpResponse::Ok().json(serde_json::json!({"success":true,"data":d}))
};
}

Can you write a macro to invoke the default() operator in rust?

Something like:
macro_rules! default(
($T:ty, $($args:expr)*) => (
$T { $($args)*, ..Default::default() };
);
)
...but with a magical type instead of expr, so you could do something like:
let p = default!(CItem, _z: ~"Hi2", x: 10);
let q = default!(CItem, _z, ~"Hi2", x, 10);
let r = default!(CItem, { _z: ~"Hi2", x: 10 });
Or something along those lines?
Is there any macro symbol that simply picks up a literal block of characters without first parsing it as a type/expr/etc?
(I realize you'd typically just write a CItem::new(), but this seems like a nice situation in some circumstances)
Macros can have multiple pattern to match the syntax, so you have to write a seperate pattern for every case seperatly like this:
macro_rules! default(
($t:ident, $($n:ident, $v:expr),*) => {
$t { $($n: $v),*, ..Default::default() }
};
($t:ident, $($n:ident: $v:expr),*) => {
default!($t, $($n, $v),*)
};
)
As you can see there are two patterns, the first matching pairs seperated by comma and the second one pairs seperated by colon.

Resources