1

I'm trying to write a Rust macro that expands to an enum whose variants contain data of types that need lifetime annotations and the compiler won't let me.

I've boiled the problem down to this minimal example:

struct A<'a> {
    s: &'a str
}

macro_rules! my_macro {
    ( $ty:ty ) => {
        enum E<'a> {
            M($ty<'a>)
        }
    }
}

my_macro!(A);

As you can see, this is meant to write out an enum E with a variant M that contains data whose type is taken from the macro parameters and is given a lifetime parameter of 'a. But trying to compile it yields a bunch of errors I find strange (I've shortened them a bit from the compiler's exact output):

error: lifetime in trait object type must be followed by `+` --> src/lib.rs:8:19
   |
8  |             M($ty<'a>)
   |                   ^^
...
13 | my_macro!(A);
   | ------------ in this macro invocation
   |
   = note: this error originates in the macro `my_macro` (in Nightly builds, run with -Z macro-backtrace for more info)

error: expected one of `)` or `,`, found `<` --> src/lib.rs:8:18
   |
8  |             M($ty<'a>)
   |                  ^
   |                  expected one of `)` or `,`
   |                  help: missing `,`

error: expected one of `)`, `+`, or `,`, found `>` --> src/lib.rs:8:21
   |
8  |             M($ty<'a>)
   |                     ^ expected one of `)`, `+`, or `,`

I don't get this at all: What trait object? There isn't one as far as I can tell.

Also, replacing M($ty<'a>) with a variant definition that doesn't use the macro type variable $ty, e.g. M(A<'a>), makes it work fine.

How can I make sense of the compiler's complaint and how can I write a macro producing the kind of enum I want?

smheidrich
  • 4,063
  • 1
  • 17
  • 30

1 Answers1

3

The problem is that A regularly wouldn't be a complete type, it's missing one of it's generic parameters to be one and thus to use it with your macro the anonymous lifetime is used, i.e. your macro invocation is equivalent to my_macro!(A<'_>). In other words $ty can't be a partially applied generic type like just A, it has to be a complete type like A<'_> and because of that $ty<'a> just semantically doesn't make any sense, just like String<'a> doesn't make sense, String doesn't take generic lifetime arguments and neither can a $ty:ty, that's why you get the errors you see.

To fix it you can simply take the most general of token types: tt

macro_rules! my_macro {
    ( $ty:tt ) => {
        enum E<'a> {
            M($ty<'a>)
        }
    }
}
cafce25
  • 15,907
  • 4
  • 25
  • 31
  • 2
    You can see it does indeed need to be a complete type in this [demo](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0e1dd8b81884116511394d276fc10697) It's just that lifetimes are special in that when they're not provided they automatically get assigned an anonymous one. I've reworded that sentence, hope it makes more sense now. – cafce25 May 12 '23 at 21:26
  • I confused myself into an [XY problem](https://en.wikipedia.org/wiki/XY_problem) because what I really wanted to do was take fully specified types as macro parameters, but misread questions like https://stackoverflow.com/questions/41603424 as stating that the only way to do that was with lifetimes as separate parameters. So the insight that `ty` fragments can and should include type arguments like lifetimes and generic type arguments not only solves the question as stated but also my original problem. Great! – smheidrich May 13 '23 at 10:56