Как использовать макросы между файлами модулей в Rust?
Проблема: макросы не экспортируются по умолчанию
В Rust макросы, объявленные через macro_rules!, по умолчанию видны только в том модуле, где они определены (и в его дочерних модулях после объявления). В вашем случае utils/mod.rs — это соседний модуль, а не дочерний, поэтому макрос не виден. Добавление #[macro_export] — правильный шаг, но его нужно применить с учётом особенностей.
Решение: используем #[macro_export] и use
Атрибут #[macro_export] делает макрос доступным на уровне крейта (crate). После этого его можно импортировать через use crate::.... Вот исправленный код:
Файл macros.rs:
#[macro_export]
macro_rules! say_hello {
() => {
println!("Hello from macro!");
};
}
Файл utils/mod.rs:
// Импортируем макрос из корня крейта
use crate::say_hello;
pub fn do_something() {
say_hello!(); // Теперь работает
}
Файл main.rs:
mod macros;
mod utils;
fn main() {
say_hello!(); // Работает (макрос уже в корне крейта)
utils::do_something();
}
Почему это работает?
#[macro_export]помещает макрос в корень крейта (как если бы он был объявлен вmain.rsилиlib.rs).use crate::say_hello;импортирует макрос в область видимости модуляutils.- В
main.rsмакрос уже виден, так как он находится в корне крейта, где объявленmain.rs.
Альтернативный подход: старый стиль с #[macro_use]
До Rust 2018 можно было использовать #[macro_use] на модуле. Этот способ устарел, но всё ещё работает:
В main.rs:
#[macro_use]
mod macros;
mod utils;
fn main() {
say_hello!(); // Работает
utils::do_something();
}
Тогда макрос будет виден во всех модулях после объявления #[macro_use]. Но в utils/mod.rs он будет доступен только если mod utils идёт после mod macros. Это менее гибко, поэтому рекомендуется современный подход с #[macro_export] + use.
Важные замечания
- Порядок объявлений модулей: в
main.rsмодули должны быть объявлены до их использования. Обычно это не проблема, если вы используете#[macro_export]. - Макросы с параметрами: если макрос принимает параметры, они также будут доступны после экспорта.
- Вложенные модули: если у вас есть
utils/sub_utils.rs, импорт будет выглядеть какuse crate::say_hello;(всё равно из корня).
Если вы хотите, чтобы макрос был доступен только внутри определённого поддерева модулей, можно не использовать #[macro_export], а переопределить его через use в нужных модулях. Но для большинства случаев экспорт на уровне крейта — самое простое решение.
Использование макросов через отдельный крейт
Если ваш проект разрастается, и макросов становится много, вы можете вынести их в отдельный крейт (библиотеку). Это даёт чёткое разделение и возможность переиспользования в других проектах.
Шаг 1: Создайте крейт для макросов
// macros_lib/src/lib.rs
#[macro_export]
macro_rules! say_hello {
() => {
println!("Hello from macro!");
};
}
#[macro_export]
macro_rules! greet {
($name:expr) => {
println!("Hello, {}!", $name);
};
}
Шаг 2: Подключите крейт в основном проекте
В Cargo.toml добавьте зависимость:
[dependencies]
macros_lib = { path = "../macros_lib" }
Шаг 3: Используйте макросы в любом модуле
// utils/mod.rs
use macros_lib::{say_hello, greet};
pub fn do_something() {
say_hello!();
greet!("Alice");
}
Преимущества такого подхода:
- Макросы изолированы в отдельном крейте, не засоряют основное пространство имён.
- Легко тестировать макросы отдельно.
- Можно публиковать крейт на crates.io для общего доступа.
Недостаток: нужно управлять ещё одним крейтом, что может быть избыточно для небольших проектов. Но для серьёзных библиотек или приложений это лучшая практика.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги