Как использовать макрос в файлах модуля?

У меня есть два модуля в отдельных файлах в одном ящике, где ящик macro_rules включен. Я хочу использовать макросы, определенные в одном модуле в другом модуле.

// 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?

Я в настоящее время попал в ошибку компилятораmacro undefined: 'my_macro'"... что имеет смысл; система макросов запускается до системы модулей. Как мне обойти это?

5 ответов

Решение
#[macro_use]
mod foo {
    macro_rules! bar {
        () => ()
    }
}

bar!();    // works

Если вы хотите использовать макрос в том же ящике, модуль, в котором определен ваш макрос, нуждается в атрибуте #[macro_use],

Если вы хотите использовать макрос в других ящиках, сам макрос также нуждается в атрибуте #[macro_export],

Если вы хотите использовать макросы из других ящиков, extern crate xxx; утверждение нуждается в атрибуте #[macro_use] импортировать все макросы из этого ящика или #[macro_use(cat, dog)] использовать только макросы cat а также dog,

Макросы можно использовать только после того, как они были определены. Это означает, что это не работает:

bar!();

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

Более подробная информация доступна в Приложении D: Макросы на языке программирования Rust.

Альтернативный подход по состоянию на (издание 2018 г.)

Обратите внимание, что хотя инструкции от @Dogbert все еще актуальны и хорошо работают, идея запоминания специальных правил размещения имен для макросов может раздражать некоторых людей.

Оказывается, что в выпуске 2018 г. и далее, поскольку версия 1.32.0of Rust, есть другой подход, который также работает, и его преимущество, imho, состоит в том, что его легче преподавать ( например, он отображает #[macro_use]устаревший). Ключевая идея заключается в следующем:

Реэкспортированный макрос ведет себя как любой другой элемент (функция, тип, константа и т . Д.): Он помещается в пространство имен в модуле, в котором происходит реэкспорт.

  • Затем к нему можно обратиться с полностью определенным путем.

  • Также может быть локально used / внесено в сферу применения, чтобы ссылаться на него безоговорочно.

Пример

      macro_rules! macro_name { ... }
pub(crate) use macro_name; // Now classic paths Just Work™

Вот и все. Довольно просто, да?


Не стесняйтесь читать дальше, но только если вас не пугает информационная перегрузка;) Я постараюсь подробно рассказать, почему, как и когда именно это работает.

Более подробное объяснение

Для реэкспорта ( pub(...) use ...) макрос, нам нужно на него сослаться! Вот где полезны правила из исходного ответа: макрос всегда может быть назван в том самом модуле, где происходит определение макроса, но только после этого определения.

      macro_rules! my_macro { ... }
my_macro!(...); // OK
      // Not OK
my_macro!(...); /* Error, no `my_macro` in scope! */
macro_rules! my_macro { ... }

Исходя из этого, мы можем повторно экспортировать макрос после определения; реэкспортированное имя само по себе не зависит от местоположения, как и все другие глобальные элементы в Rust 🙂

  • Таким же образом, как и мы:

            mod foo {
        pub struct A {}
    }
    
    fn main() {
        let _: A;
    }
    
    type A = foo::A;
    
  • Мы также можем:

            mod foo {
        pub struct A {}
    }
    
    fn main() {
        let _: A;
    }
    
    use foo::A /* as A */;
    
  • То же самое относится и к другим элементам, таким как функции, но также и к макросам!

            fn main() {
        a!();
    }
    
    macro_rules! foo { ... } // foo is only nameable *from now on*
    use foo as a;            // but `a` is now visible all around the module scope!
    

    И получается, что мы можем написать use foo as foo;, или обычное use foo; стенография, и она до сих пор работает.

Остается только один вопрос: или ?

  • Для макросов -ed вы можете использовать любую конфиденциальность, которую хотите; обычно pub.

  • Для других макросов вы не можете перейти выше pub(crate).


Подробные примеры

  • Для нередактируемого макроса

            mod foo {
        use super::example::my_macro;
    
        my_macro!(...); // OK
    }
    
    mod example {
        macro_rules! my_macro { ... }
        pub(crate) use my_macro;
    }
    
    example::my_macro!(...); // OK
    
  • Для макроса -ed

    Применение к определению макроса делает его видимым после того самого модуля, в котором он определен (чтобы соответствовать поведению не- #[macro_export]ed macros), но он также помещает макрос в корень ящика (где макрос определен) по абсолютному пути .

    Это означает, что pub use macro_name; сразу после определения макроса или pub use crate::macro_name; в любом модуле этого ящика будет работать.

    • Примечание: чтобы реэкспорт не противоречил механике «экспортировано в корень ящика», его нельзя делать в корне самого ящика.
            pub mod example {
        #[macro_export] // macro nameable at `crate::my_macro`
        macro_rules! my_macro { ... }
        pub use my_macro; // macro nameable at `crate::example::my_macro`
    }
    
    pub mod foo {
        pub use crate::my_macro; // macro nameable at `crate::foo::my_macro`
    }
    

При использовании pub / pub(crate) use macro_name;, имейте в виду, что с учетом того, как пространства имен работают в Rust, вы также можете повторно экспортировать константы / функции или типы / модули. Это также вызывает проблемы с глобально доступными макросами, такими как #[test], #[allow(...)], #[warn(...)]и т. д.

Чтобы решить эти проблемы, помните, что вы можете переименовать элемент при его повторном экспорте:

      macro_rules! __test__ { ... }
pub(crate) use __test__ as test; // OK

macro_rules! __warn__ { ... }
pub(crate) use __warn__ as warn; // OK

Также могут срабатывать некоторые ложноположительные линты:

  • от курка-счастливого clippy инструмент, когда этот трюк выполняется любым способом;

  • из rustc сам, когда это делается на macro_rules! определение, которое происходит внутри тела функции.

Этот ответ устарел в Rust 1.1.0-stable.


Вам нужно добавить #![macro_escape] на вершине macros.rs и включите его, используя mod 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

Для дальнейшего использования

$ rustc -v
rustc 0.13.0-dev (2790505c1 2014-11-03 14:17:26 +0000)

Добавление #![macro_use] в начало вашего файла, содержащего макросы, все макросы будут загружены в main.rs.

Например, давайте предположим, что этот файл называется node.rs:

#![macro_use]

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

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

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

Ваш main.rs будет выглядеть примерно так:

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

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

Наконец, предположим, у вас есть файл с именем toad.rs, который также требует следующие макросы:

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

pub fn a_thing() {
  test!();

  node::fun_times();
}

Обратите внимание, что как только файлы загружаются в main.rs с modостальные файлы имеют доступ к ним через use ключевое слово.

Я столкнулся с той же проблемой в Rust 1.44.1, и это решение работает для более поздних версий (известно, что оно работает в Rust 1.7).

Допустим, у вас есть новый проект:

src/
    main.rs
    memory.rs
    chunk.rs

В main.rs вам нужно отметить, что вы импортируете макросы из источника, иначе это вам не поможет.

#[macro_use]
mod memory;
mod chunk;

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

Итак, в memory.rs вы можете определять макросы, и вам не нужны аннотации:

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

Наконец, вы можете использовать его в chunk.rs, и вам не нужно включать макрос сюда, потому что это делается в main.rs:

grow_capacity!(8);

Upvoted ответ вызвал замешательство у меня с этим документом на примере, было бы полезно тоже.

Другие вопросы по тегам