3 votes

Macro Rust acceptant un argument avec deux points, une structure qui est à l'intérieur d'un module.

Le code suivant fonctionne :

pub struct Bar {
    pub name: String
}

macro_rules! printme {
    ($myclass: ident) => {
        let t = $myclass { name: "abc".to_owned() };
        println!("{}", t.name);
    }
}

fn main() {
    printme!(Bar);
}

Toutefois, si Bar est à l'intérieur d'un module, cela ne fonctionnera pas, l'erreur est la suivante no rules expected the token :: :

mod foo {
    pub struct Bar {
        pub name: String
    }
}

macro_rules! printme {
    ($myclass: ident) => {
        let t = $myclass { name: "abc".to_owned() };
        println!("{}", t.name);
    }
}

fn main() {
    printme!(foo::Bar); // not allowed
}

Cela ne fonctionne que si j'utilise un alias :

fn main() {
    use foo::Bar as no_colon;
    printme!(no_colon);
}

Y a-t-il un moyen de le faire fonctionner avec le colon, sans le use alias ?

9voto

rodrigo Points 34500

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`

3voto

phimuemue Points 11644

Avec un peu d'astuce, vous pouvez le faire fonctionner :

mod foo {
    pub struct Bar {
        pub name: String
    }
}

macro_rules! printme {
    ($myclass: ty) => {
        type LocalT = $myclass;
        let t = LocalT { name: "abc".to_owned() };
        println!("{}", t.name);
    }
}

fn main() {
    printme!(foo::Bar);
}
  • accepter ty (type) au lieu de ident (identifiant)
  • Je ne sais pas pourquoi, mais je n'ai pas pu le faire fonctionner sans LocalT

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X