Constructing enum variant in a declarative macro

I have this macro, to construct similarly structured enum variants:

macro_rules! create_instance {
    ($e: path) => { $e { v: 42 } }
}

fn main() {
    let s = create_instance!(E::I32);
    println!("{:?}", s);
}


#[derive(Debug)]
enum E {
    I32 { v: i32 },
    U32 { v: u32 },
    I64 { v: i64 },
    U64 { v: u64 },
}

But it fails with a weird error:

error: macro expansion ignores token `{` and any following
 --> src/main.rs:2:24
  |
2 |     ($e: path) => { $e { v: 42 } }
  |                        ^
...
6 |     let s = create_instance!(E::I32);
  |             ------------------------ caused by the macro expansion here
  |
  = note: the usage of `create_instance!` is likely invalid in expression context


error[E0533]: expected value, found struct variant `E::I32`
 --> src/main.rs:2:21
  |
2 |     ($e: path) => { $e { v: 42 } }
  |                     ^^ not a value
...
6 |     let s = create_instance!(E::I32);
  |             ------------------------ in this macro invocation
  |
  = note: this error originates in the macro `create_instance` (in Nightly builds, run with -Z macro-backtrace for more info)

Why is this happening? Maybe it's a hygiene thing? Is there a way to overcome this in a declarative macro?

No, it's purely syntactic. (You could argue that hygiene is a purely syntactic concept, too – in which case my answer is "this is lower-level than hygiene".)

An expression-position macro must expand to something that looks like a complete, atomic expression in order to avoid ambiguities, so placing arbitrary trailing braces (that could denote a block, the body of a trait, etc.) at the end is not allowed.

When in doubt, use more indirection: you can always work around this by wrapping the thing into a block or parentheses, either of which will cause it to become a single token tree that yields the inner value.

However, if you do that (slightly modified Playground), you'll get an error about a "struct literal body without a path".

(Now I think that may actually be hygiene kicking in: the identifier for the path comes from the call site, while its fields come from the macro expansion.) No it's still not hygiene; if you send the field name from the caller too, it still doesn't work.

I don't know how to fix this; I'd probably scrap the current approach and write a proc-macro or do it with a function/trait, at runtime.

1 Like

that's the correct solution. you just add an extra pair of braces so it can be used at expression position:

macro_rules! create_instance {
    ($e: path) => {{ $e { v: 42 } }}
}
2 Likes

@nerditation, thanks so much. Now I'm considering just always putting double curlies in declarative macros.

I wonder why that didn't work with regular parenthesis, though?

sorry, I don't really know the technicality here. I just happened to know braces would work in this situation, out of past experience.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.