I have a macro that implements a trait, impl_Trait!()
. Right now, it works for types without generic parameters, but I'm not sure how to add the type parameters to the impl
keyword.
macro_rules! impl_FooTrait {
($name:ty) => {
impl $crate::FooTrait for $name { ... }
};
}
struct Bar(i32);
impl_FooTrait!(Bar);
// All OK
struct Baz<'a>(&'a i32);
impl_FooTrait!(Baz<'a>);
// use of undeclared lifetime name `'a`
Short for “type,” T is the default choice of most Rust programmers. When we use a parameter in the body of the function, we have to declare the parameter name in the signature so the compiler knows what that name means.
The most widely used form of macros in Rust is the declarative macro. These are also sometimes referred to as “macros by example,” “ macro_rules! macros,” or just plain “macros.” At their core, declarative macros allow you to write something similar to a Rust match expression.
Another important difference between macros and functions is that you must define macros or bring them into scope before you call them in a file, as opposed to functions you can define anywhere and call anywhere. Save this answer.
In Rust, generics refer to the parameterization of data types and traits. Generics allows to write more concise and clean code by reducing code duplication and providing type-safety. The concept of Generics can be applied to methods, functions, structures, enumerations, collections and traits.
You could use a tt
(single token) identifier to accept a lifetime you want in another macro arm (playground link)
macro_rules! impl_FooTrait {
($name:ty, $lifetime:tt) => {
impl<$lifetime> $crate::FooTrait for $name { }
};
($name:ty) => {
impl $crate::FooTrait for $name { }
};
}
struct Bar(i32);
impl_FooTrait!(Bar);
struct Baz<'a>(&'a i32);
impl_FooTrait!(Baz<'a>, 'a); // Use and declare the lifetime during macro invocation
Here is an example that actually implements something.
Its a bit weird to look at I guess. I am interested to see any alternative answers. There is possibly a nicer way to do this; I'm not well versed in macro land yet.
First of all parsing generics with macro_rules!
in a fool proof way is extremely difficult (might be impossible), because patterns do not support mixed repetitions
(e.g. $( $( $lt:lifetime ) | $( $gen:ident )* )*
, which would match either a lifetime ('a
) or a generic parameter (T
)).
If this is required, you should consider using a proc-macro
(you can even put them in expression position by using proc-macro-hack
).
Simply putting the code here without an explanation would benefit nobody, so the below goes through all the steps that are required to understand the final declarative macro :)
Parsing an input in the form of Hello<'a, 'b>
or Hello
is relatively simple:
macro_rules! simple_match {
(
// name of the struct/enum
$name:ident
// only one or none `<>`
$(<
// match one or more lifetimes separated by a comma
$( $lt:lifetime ),+
>)?
) => {}
}
simple_match!( Hello<'a, 'b, 'static> );
One might also have constrained lifetimes (e.g. Hello<'a, 'b: 'a, 'static>
), which can not be parsed with the above.
To parse this too, the following pattern would have to be added to the end of $lt:lifetime
:
// optional constraint: 'a: 'b
$( : $clt:lifetime )?
macro_rules! better_match {
(
// name of the struct/enum
$name:ident
// only one or none `<>`
$(<
// match one or more lifetimes separated by a comma
$(
$lt:lifetime
// optional constraint: 'a: 'b
$( : $clt:lifetime )?
),+
>)?
) => {}
}
better_match!( Hello<'a, 'b: 'static> );
The above is limited to only a single constrained lifetime (Hello<'a: 'b + 'c>
would fail to parse). In order to support multiple constrained lifetimes one has to change the pattern to:
$(
: $clt:lifetime
// allow `'z: 'a + 'b + 'c`
$(+ $dlt:lifetime )*
)?
and that is everything needed for parsing generic lifetimes. One could also try parsing higher ranked lifetimes, but this is would make the pattern even more complex.
So the final macro for parsing lifetimes looks like this
macro_rules! lifetimes {
( $name:ident $(< $( $lt:lifetime $( : $clt:lifetime $(+ $dlt:lifetime )* )? ),+ >)? ) => {}
}
lifetimes!( Hello<'b, 'a: 'b, 'static, 'c: 'a + 'b> );
The above macro does only allow lifetimes, which can be fixed by replacing lifetime
with tt
in the pattern (both lifetimes and generic params can be parsed as a tt
):
macro_rules! generic {
( $name:ident $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? ) => {}
}
generic!( Hello<'b, 'a: 'b, 'static, 'c: 'a + 'b> );
generic!( Hello<T: Display, D: Debug + 'static + Display, 'c: 'a + 'b> );
Like I mentioned above, I think it is currently impossible to differentiate between a lifetime and a trait bound. If this is required one could do it partially with ( $(+ $lt:lifetime )* $(+ $param:ident )* )
, but this would not work for unsorted bounds like Hello<'a, T, 'b>
or T: 'a + Debug + 'c
.
The impl_trait
-macro would then be written like this:
use std::fmt::{Debug, Display};
trait ExampleTrait {}
struct Alpha;
struct Beta<'b>(&'b usize);
struct Gamma<T>(T);
struct Delta<'b, 'a: 'static + 'b, T: 'a, D: Debug + Display + 'a> {
hello: &'a T,
what: &'b D,
}
macro_rules! impl_trait {
( $name:ident $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? ) => {
// I split this over multiple lines to make it more readable...
// this is essentially just a copy of the above match without the
// type annotations
impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?
ExampleTrait
for $name
// the bounds are not required here
$(< $( $lt ),+ >)?
{}
}
}
impl_trait!(Alpha);
impl_trait!(Beta<'b>);
impl_trait!(Gamma<T>);
impl_trait!(Delta<'b, 'a: 'static + 'b, T: 'a, D: Debug + Display + 'a>);
Note: Paths are not supported (for ex. impl_trait!(Hello<D: std::fmt::Display>)
The below macro works with multiple structs in a call:
macro_rules! impl_trait_all {
( $( $name:ident $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? ),+ ) => {
$(
// I split this over multiple lines to make it more readable...
// this is essentially just a copy of the above match without the
// type annotations
impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?
ExampleTrait
for $name
// the bounds are not required here
$(< $( $lt ),+ >)?
{}
)+
}
}
impl_trait_all!(
Alpha,
Beta<'b>,
Gamma<T>,
Delta<'b, 'a: 'static + 'b, T: 'a, D: Debug + Display + 'a>
);
Link to playground with all the code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With