Quand vous écrivez ($myclass: ident)
vous dites que l'utilisateur doit écrire un identifiant à cet endroit de l'invocation de la macro. Et comme vous l'avez noté, Bar
est un identifiant, mais foo::Bar
ne l'est pas : syntaxiquement, ce type de liste-d'identifiants-séparés-par-double-colonne est appelé chemin .
Vous pouvez écrire ($myclass: path)
ou si vous voulez vous limiter aux types existants, vous pouvez écrire ($myclass: ty)
comme le suggère la réponse de @phimuemue. Mais si vous faites cela, vous échouerez en essayant d'utiliser ce type pour construire l'objet. Cela est dû à la façon dont l'analyseur syntaxique fonctionne : il doit analyser le chemin d'accès et l'attribut {
dans la même arborescence de jetons, mais ayant l'attribut path
o ty
a rompu le lien avec le {
. Comme il s'agit simplement d'une limitation de l'analyseur syntaxique, et non d'une limitation sémantique, vous pouvez utiliser un alias local comme solution de rechange, comme le suggère l'autre réponse.
Cependant, je suggère d'utiliser une solution basée sur les traits si possible. Je considère que c'est beaucoup plus idiomatique :
trait Nameable {
fn new(name: &str) -> Self;
}
mod foo {
pub struct Bar {
pub name: String
}
impl super::Nameable for Bar {
fn new(name: &str) -> Bar {
Bar {
name: name.to_string()
}
}
}
}
macro_rules! printme {
($myclass: ty) => {
let t = <$myclass as Nameable>::new("abc");
println!("{}", t.name);
}
}
fn main() {
printme!( foo::Bar );
}
Ou bien vous pouvez sortir l'outil ultime des macros Rust : la fonction liste d'arbres de tokens qui peut analyser presque tout :
macro_rules! printme {
($($myclass: tt)*) => {
let t = $($myclass)* { name: "abc".to_string() };
println!("{}", t.name);
}
}
Lorsque vous invoquez cette macro avec printme!(foo::Bar)
il sera en fait analysé comme une liste de trois arbres à jetons : foo
, ::
y Bar
et votre construction de l'objet fonctionnera.
L'inconvénient (ou l'avantage) de cette méthode est qu'elle consommera tous vos jetons, peu importe ce que vous écrivez dans la macro, et si elle échoue, elle émettra un message d'erreur bizarre à l'intérieur de votre macro, au lieu de dire que votre jeton n'est pas valide dans cette invocation de macro.
Par exemple, en écrivant printme!( foo::Bar {} )
avec ma macro basée sur les traits donne l'erreur la plus utile :
error: no rules expected the token `{`
--> src/main.rs:27:24
|
19 | macro_rules! printme {
| -------------------- when calling this macro
...
27 | printme!( foo::Bar {} );
| ^ no rules expected this token in macro call
Alors que l'écriture du même code avec la macro token-tree-list produit quelques messages pas très utiles :
warning: expected `;`, found `{`
--> src/main.rs:21:30
|
21 | let t = $($myclass)* { name: "abc".to_string() };
| ^
...
27 | printme!( foo::Bar {} );
| ------------------------ in this macro invocation
|
= note: this was erroneously allowed and will become a hard error in a future release
error: expected type, found `"abc"`
--> src/main.rs:21:38
|
21 | let t = $($myclass)* { name: "abc".to_string() };
| - ^^^^^ expected type
| |
| tried to parse a type due to this
...
27 | printme!( foo::Bar {} );
| ------------------------ in this macro invocation
error[E0063]: missing field `name` in initializer of `foo::Bar`
--> src/main.rs:27:15
|
27 | printme!( foo::Bar {} );
| ^^^^^^^^ missing `name`