Avatar ·

Как использовать макросы между файлами модулей в Rust?

Всем привет! Я новичок в Rust и столкнулся с проблемой при попытке использовать макросы в разных файлах моего проекта. У меня есть библиотека с модульной структурой, и я хочу определить макрос в одном файле модуля, а затем использовать его в другом. Вот упрощённая структура моего проекта: ``` src/ ├── main.rs ├── macros.rs └── utils/ └── mod.rs ``` В файле `macros.rs` я объявил простой макрос: ```rust macro_rules! say_hello { () => { println!("Hello from macro!"); }; } ``` Затем в `main.rs` я подключаю модули так: ```rust mod macros; mod utils; fn main() { say_hello!(); // Работает! utils::do_something(); } ``` А в `utils/mod.rs` я пытаюсь вызвать тот же макрос: ```rust pub fn do_something() { say_hello!(); // Ошибка: macro is not found } ``` Компилятор выдаёт ошибку `error: cannot find macro 'say_hello' in this scope`. Я читал, что макросы в Rust работают по-особенному, но не понимаю, как правильно их экспортировать. Пробовал добавить `#[macro_export]` — не помогло. В чём проблема и как сделать макрос доступным во всех модулях проекта? Буду благодарен за объяснение с примерами. <<>>

Проблема: макросы не экспортируются по умолчанию

В 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 для общего доступа.

Недостаток: нужно управлять ещё одним крейтом, что может быть избыточно для небольших проектов. Но для серьёзных библиотек или приложений это лучшая практика.