Avatar ·

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

Привет! Я изучаю Rust и столкнулся с проблемой при попытке вынести макросы в отдельные модули. У меня есть проект, где я определил полезный макрос `my_println!` в файле `main.rs`. Я хочу использовать его в других модулях, которые лежат в отдельных файлах. Вот моя структура проекта: ``` src/ main.rs utils/ mod.rs helper.rs ``` В `main.rs` я определил макрос: ```rust macro_rules! my_println { ($($arg:tt)*) => { println!("[LOG] {}", format!($($arg)*)); }; } mod utils; ``` В `utils/mod.rs` я объявил подмодуль: ```rust pub mod helper; ``` В `utils/helper.rs` я пытаюсь использовать макрос: ```rust pub fn do_something() { my_println!("Hello from helper!"); } ``` Но компилятор выдаёт ошибку: `error: cannot find macro 'my_println' in this scope`. Я читал, что макросы в Rust не экспортируются по умолчанию, как функции. Как правильно организовать код, чтобы макрос был доступен во всех модулях проекта? Нужно ли использовать `#[macro_export]` или есть другие способы? Объясните, пожалуйста, подробно, как это работает. <<>>

Проблема экспорта макросов в Rust

В Rust макросы (определённые через macro_rules!) по умолчанию видны только в том модуле, где они объявлены, и в его дочерних модулях (после объявления макроса). Это отличается от функций, которые можно сделать публичными через pub. Для макросов работает другой механизм — атрибут #[macro_export].

Решение: используйте #[macro_export]

Чтобы макрос был доступен во всех модулях вашего проекта (и даже для внешних крейтов), нужно пометить его атрибутом #[macro_export]. Вот как исправить ваш код:

1. В main.rs (или отдельном файле для макросов):

// main.rs
#[macro_export]
macro_rules! my_println {
    ($($arg:tt)*) => {
        println!("[LOG] {}", format!($($arg)*));
    };
}

mod utils;

2. В utils/helper.rs макрос теперь будет доступен:

// utils/helper.rs
pub fn do_something() {
    my_println!("Hello from helper!");
}

Как это работает?

  • #[macro_export] делает макрос доступным в корне вашего крейта (crate root). После этого его можно использовать в любом модуле, просто обращаясь по имени.
  • Макросы с #[macro_export] автоматически становятся публичными и доступны для других крейтов, если ваш крейт используется как зависимость.
  • Важно: макрос должен быть определён до того, как он используется в модулях. Обычно их помещают в начало файла lib.rs или main.rs.

Альтернативный способ: вынести макросы в отдельный модуль

Если макросов много, удобнее вынести их в отдельный файл, например macros.rs, и подключить его через #[macro_use]:

// src/macros.rs
#[macro_export]
macro_rules! my_println {
    ($($arg:tt)*) => {
        println!("[LOG] {}", format!($($arg)*));
    };
}

// src/main.rs
#[macro_use]
mod macros;
// Теперь макросы из macros.rs доступны во всём крейте

mod utils;

Атрибут #[macro_use] перед mod macros; импортирует все экспортированные макросы из этого модуля в текущую область видимости.

Важные нюансы

  • Порядок объявления: макросы должны быть объявлены до их использования в коде модулей. Если модуль определён раньше макроса, компилятор выдаст ошибку.
  • Макросы и pub: атрибут #[macro_export] — это единственный способ сделать макрос публичным. Просто написать pub macro_rules! нельзя.
  • Для внешних крейтов: если вы пишете библиотеку, экспортированные макросы будут доступны пользователям вашего крейта через use my_crate::my_macro; (начиная с Rust 2018).

Теперь ваш макрос my_println! будет работать в utils/helper.rs и любом другом модуле проекта.

<<>>

Использование #[macro_use] на уровне модуля

Если вы не хотите помечать каждый макрос атрибутом #[macro_export], можно использовать атрибут #[macro_use] на объявлении модуля. Это импортирует все макросы из указанного модуля (даже неэкспортированные) в текущую область видимости.

Пример:

// src/macros.rs — без #[macro_export]
macro_rules! my_println {
    ($($arg:tt)*) => {
        println!("[LOG] {}", format!($($arg)*));
    };
}

// src/main.rs
#[macro_use]
mod macros; // Импортируем все макросы из macros.rs

mod utils;

// Теперь my_println! доступен и в main.rs, и в utils/helper.rs

Недостаток: такой подход работает только в пределах одного крейта. Если вы хотите, чтобы макрос был доступен из других крейтов (например, при публикации библиотеки), обязательно используйте #[macro_export].

Дополнительный совет: в Rust 2018+ можно использовать macro_use также в выражении extern crate, чтобы импортировать макросы из внешних зависимостей, но для внутренних модулей описанный выше способ — самый простой.