Как использовать макрос в файлах модуля?
У меня есть два модуля в отдельных файлах в одном ящике, где ящик 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.0
of Rust, есть другой подход, который также работает, и его преимущество, imho, состоит в том, что его легче преподавать ( например, он отображает
#[macro_use]
устаревший). Ключевая идея заключается в следующем:
Реэкспортированный макрос ведет себя как любой другой элемент (функция, тип, константа и т . Д.): Он помещается в пространство имен в модуле, в котором происходит реэкспорт.
Затем к нему можно обратиться с полностью определенным путем.
Также может быть локально
use
d / внесено в сферу применения, чтобы ссылаться на него безоговорочно.
Пример
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 ответ вызвал замешательство у меня с этим документом на примере, было бы полезно тоже.