Comment utiliser une macro dans les fichiers de module?

93

J'ai deux modules dans des fichiers séparés dans la même caisse, où la caisse a été macro_rulesactivée. Je souhaite utiliser les macros définies dans un module dans un autre module.

// macros.rs
#[macro_export] // or not? is ineffectual for this, afaik
macro_rules! my_macro(...)

// something.rs
use macros;
// use macros::my_macro; <-- unresolved import (for obvious reasons)
my_macro!() // <-- how?

J'ai actuellement rencontré l'erreur du compilateur " macro undefined: 'my_macro'" ... ce qui est logique; le système de macros s'exécute avant le système de modules. Comment contourner ce problème?

utilisateur
la source
Ne devriez-vous pas utilisermodule::my_macro!()?
u_mulder
2
non (pas afaik) - le préfixe du module serait ignoré (selon le message du compilateur).
utilisateur

Réponses:

131

Macros dans la même caisse

#[macro_use]
mod foo {
    macro_rules! bar {
        () => ()
    }
}

bar!();    // works

Si vous souhaitez utiliser la macro dans la même caisse, le module dans lequel votre macro est définie a besoin de l'attribut #[macro_use].

Les macros ne peuvent être utilisées qu'après avoir été définies. Cela signifie que cela ne fonctionne pas:

bar!();  // ERROR: cannot find macro `bar!` in this scope

#[macro_use]
mod foo {
    macro_rules! bar {
        () => ()
    }
}

Macros dans les caisses

Pour utiliser votre macro_rules!macro à partir d'autres caisses, la macro elle-même a besoin de l'attribut #[macro_export]. La caisse d'importation peut ensuite importer la macro via use crate_name::macro_name;.

Caisse util

#[macro_export]
macro_rules! foo {
    () => ()
}

Caisse user

use util::foo;

foo!();

Notez que les macros se trouvent toujours au niveau supérieur d'une caisse; donc même si fooc'était à l'intérieur d'un mod bar {}, la usercaisse devrait encore écrire use util::foo;et non use util::bar::foo; .

Avant Rust 2018, vous deviez importer une macro à partir d'autres caisses en ajoutant l'attribut #[macro_use]à l' extern crate util;instruction. Cela importerait toutes les macros à partir de util. Alternativement, #[macro_use(cat, dog)]pourrait être utilisé pour importer uniquement les macros catet dog. Cette syntaxe ne devrait plus être nécessaire.

Plus d'informations sont disponibles dans le chapitre du langage de programmation Rust sur les macros .

Lukas Kalbertodt
la source
27
"Les macros ne peuvent être utilisées qu'après avoir été définies." - Ceci est essentiel car vous pouvez rencontrer cette erreur même si vous avez fait toutes les autres choses mentionnées correctement. Par exemple, si vous avez des modules macroset foo(qui utilise une macro de macros), et que vous les listez par ordre alphabétique dans votre lib.rs ou main.rs, foo sera chargé avant les macros et le code ne sera pas compilé.
neverfox
7
^ conseil de pro - ça m'a totalement eu
semore_1267
3
Notez également que pour utiliser des macros en interne, l' #[macro_use]attribut doit être sur chaque module et module parent, etc. jusqu'à ce qu'il atteigne le point où vous devez l'utiliser.
10
Cette réponse n'a pas fonctionné pour moi. Le module qui a déclaré la macro avait #[macro_use]et il a été déclaré en premier dans lib.rs - ne fonctionnait toujours pas. La réponse de @ Ten m'a aidé et j'ai ajouté #[macro_use]en haut de lib.rs - puis cela a fonctionné. Mais je ne suis toujours pas sûr de la meilleure pratique puisque j'ai lu ici que "Vous n'importez pas de macros d'autres modules; vous exportez la macro depuis le module de définition"
Sorin Bolos
J'oublie toujours comment les macros de Rust fonctionnent avec les modules. C'est un système horrible et j'espère qu'il y en aura un meilleur un jour.
Hutch Moore
20

Cette réponse est obsolète depuis Rust 1.1.0-stable.


Vous devez l'ajouter #![macro_escape]en haut de macros.rset l'inclure en utilisant mod macros;comme mentionné dans le Guide des macros .

$ cat macros.rs
#![macro_escape]

#[macro_export]
macro_rules! my_macro {
    () => { println!("hi"); }
}

$ cat something.rs
#![feature(macro_rules)]
mod macros;

fn main() {
    my_macro!();
}

$ rustc something.rs
$ ./something
hi

Pour référence future,

$ rustc -v
rustc 0.13.0-dev (2790505c1 2014-11-03 14:17:26 +0000)
Dogbert
la source
J'avais totalement raté cet attribut. Merci!
utilisateur
4
BTW, l' #[macro_export]attribut n'est pas nécessaire ici. Il n'est nécessaire que si la macro doit être exportée vers des utilisateurs de crate externes. Si la macro n'est utilisée qu'à l'intérieur de la caisse, elle #[macro_export]n'est pas nécessaire.
Vladimir Matveev
1
Merci beaucoup pour la réponse. Je veux juste ajouter que si votre something.rsfichier utilise d'autres modules, par exemple avec mod foobar;, et que ce foobarmodule utilise les macros de macro.rs, alors vous devez mettre mod macro; avant mod foobar; pour que le programme se compile. Chose mineure, mais ce n'est pas une IMO évidente.
conradkleinespel
2
(nb cette réponse est maintenant obsolète; j'ai accepté la réponse mise à jour donnée par Lukas)
utilisateur
7

L'ajout #![macro_use]en haut de votre fichier contenant des macros entraînera l'extraction de toutes les macros dans main.rs.

Par exemple, supposons que ce fichier s'appelle node.rs:

#![macro_use]

macro_rules! test {
    () => { println!("Nuts"); }
}

macro_rules! best {
    () => { println!("Run"); }
}

pub fn fun_times() {
    println!("Is it really?");
}

Votre main.rs ressemblerait parfois à ce qui suit:

mod node;  //We're using node.rs
mod toad;  //Also using toad.rs

fn main() {
    test!();
    best!();
    toad::a_thing();
}

Enfin, disons que vous avez un fichier appelé toad.rs qui nécessite également ces macros:

use node; //Notice this is 'use' not 'mod'

pub fn a_thing() {
  test!();

  node::fun_times();
}

Notez qu'une fois que les fichiers sont extraits dans main.rs avec mod, le reste de vos fichiers y a accès via le usemot - clé.

Luke Dupin
la source
J'ai ajouté plus de précisions. Depuis rustc 1.22.1, cela fonctionne.
Luke Dupin
Êtes-vous sûr? Où est-ce #! [Macro_use] (et non # [macro_use]) documenté? Je ne peux pas le trouver. Ça ne marche pas ici.
Markus
Cela a fonctionné quand je l'ai posté, le système d'inclusion de Rust est un tel désordre, il est tout à fait possible que cela ne fonctionne plus.
Luke Dupin
@Markus Notez que l' #![macro_use]instruction est À L' INTÉRIEUR du macro-module, pas à l'extérieur. La #![...]syntaxe correspond au fait que les attributs s'appliquent à leurs portées contenant, par exemple #![feature(...)](évidemment, cela n'aurait pas de sens s'il était écrit comme #[feature(...)]; cela exigerait sémantiquement que le compilateur active certaines fonctionnalités sur des éléments spécifiques dans une crate, plutôt que sur toute la crate racine). Donc, comme l'a dit @LukeDupin, le système de modules est en désordre, mais peut-être pour une raison différente de celle à première vue.
utilisateur
Je souhaite que cette réponse mentionne comment la construction n'est pas exactement idiomatique (cela mis à part, j'aime la réponse). Malgré sa (non) -idiomaticité, c'est intéressant car le placer à côté de la forme idiomatique rend douloureusement évident que les macros interagissent avec le système de modules d'une manière différente des constructions habituelles. Ou au moins il dégage une forte odeur (comme vient de le démontrer @Markus ayant un reproche avec lui).
utilisateur du
2

J'ai rencontré le même problème dans Rust 1.44.1, et cette solution fonctionne pour les versions ultérieures (connue pour Rust 1.7).

Disons que vous avez un nouveau projet en tant que:

src/
    main.rs
    memory.rs
    chunk.rs

Dans main.rs , vous devez annoter que vous importez des macros à partir de la source, sinon cela ne le fera pas pour vous.

#[macro_use]
mod memory;
mod chunk;

fn main() {
    println!("Hello, world!");
}

Ainsi, dans memory.rs, vous pouvez définir les macros, et vous n'avez pas besoin d'annotations:

macro_rules! grow_capacity {
    ( $x:expr ) => {
        {
            if $x < 8 { 8 } else { $x * 2 }
        }
    };
}

Enfin, vous pouvez l'utiliser dans chunk.rs , et vous n'avez pas besoin d'inclure la macro ici, car c'est fait dans main.rs:

grow_capacity!(8);

La réponse positive a semé la confusion chez moi, avec ce document par exemple , ce serait également utile.

knh190
la source
La réponse acceptée littéralement a que les premières lignes du premier bloc de code: #[macro_use] mod foo {.
Shepmaster
1
@Shepmaster la réponse votée a la définition des macros et la déclaration d'importation au même endroit, donc cela a semé la confusion (pour moi). J'utilisais #[macro_use]dans la définition. Le compilateur ne dit pas qu'il est mal placé.
knh190
Vous voudrez peut-être relire doc.rust-lang.org/book/… alors.
Shepmaster
Merci pour cette réponse! J'ai également été déconcerté par la réponse acceptée et je n'ai pas pu la comprendre avant d'avoir lu votre explication.
Prgrm.celeritas
@Shepmaster Il n'y a aucune mention du fonctionnement des macros dans la section vers laquelle vous créez un lien. Vouliez-vous créer un lien vers une autre partie du livre?
detly