Макрос принимает тип ржавчины с общими параметрами
У меня есть макрос, который реализует черту, impl_Trait!()
, Прямо сейчас это работает для типов без общих параметров, но я не уверен, как добавить параметры типа к impl
ключевое слово.
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`
2 ответа
Отправляя это как ответ с отказом от ответственности: возможно, есть более хороший способ сделать это. Я еще не очень хорошо разбираюсь в макро-земле.
Вы могли бы использовать tt
(одиночный токен) идентификатор для принятия нужной вам жизни в другом макросе ( ссылка на игровую площадку)
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
Я думаю, это немного странно. Мне интересно видеть любые другие ответы, у которых есть альтернативы.
Вот пример, который на самом деле что-то реализует: Playground link
Прежде всего, разбор дженериков с помощью macro_rules!
надежным способом чрезвычайно сложно (может быть невозможно), потому что шаблоны не поддерживают смешанные повторения (например, $( $( $lt:lifetime ) | $( $gen:ident )* )*
, что соответствует либо времени жизни ('a
) или общий параметр (T
)).
Если это необходимо, вам следует рассмотреть возможность использования proc-macro
(вы даже можете поместить их в позицию выражения, используя proc-macro-hack
).
Простое размещение кода здесь без объяснения никому не принесет пользы, поэтому ниже описаны все шаги, необходимые для понимания окончательного декларативного макроса:)
Разбор ввода в виде Hello<'a, 'b>
или Hello
относительно просто:
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> );
Можно также ограничить время жизни (например, Hello<'a, 'b: 'a, 'static>
), который нельзя разобрать с вышеупомянутым.
Чтобы разобрать и это, нужно добавить следующий шаблон в конец $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> );
Вышеуказанное ограничено только одним ограниченным сроком службы (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>)
Приведенный ниже макрос работает с несколькими структурами в вызове:
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>
);
Это старый вопрос, но, похоже, здесь нет хорошего ответа. У меня есть частичное решение, хотя оно соответствует некоторому некорректному вводу, и я не могу заставить его работать для параметров времени жизни.
#[macro_export]
macro_rules! impl_trait {
// this evil monstrosity matches <A, B: T, C: S+T>
// but because there is no "zero or one" rule, also <D: S: T>
($ty:ident < $( $N:ident $(: $b0:ident $(+$b:ident)* )* ),* >) =>
{
impl< $( $N $(: $b0 $(+$b)* )* ),* >
$crate::path::to::Trait
for $ty< $( $N ),* >
{
// function implementations go here
}
};
// match when no type parameters are present
($ty:ident) => {
impl_trait!($ty<>);
};
}