Как посмотреть развёрнутый макрос, из-за которого возникает ошибка компиляции в Rust?
Использование флага --expand в nightly-версии компилятора
Самый прямой способ увидеть развёрнутый код макроса — использовать флаг -Z macro-backtrace или cargo expand. Начнём с первого варианта, который доступен в nightly-версии Rust.
Установите nightly-канал (если ещё не сделали этого):
rustup toolchain install nightly
Затем запустите компиляцию с флагом -Z macro-backtrace:
rustc +nightly -Z macro-backtrace main.rs
Это покажет полный стек макросов, включая их развёрнутый код. Для вашего примера с impl_display! вывод будет содержать:
error[E0277]: `Point` doesn't implement `std::fmt::Display`
--> main.rs:15:1
|
15 | impl_display!(Point, "Point({}, {})");
| ------------------------------------- in this macro invocation
|
= note: required by `std::fmt::Display::fmt`
= note: this error originates in the macro `impl_display` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
The expanded code is:
impl std::fmt::Display for Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Point({}, {})", self)
}
}
The problem is that `write!` macro expects a format string and arguments, but you passed `self` as a single argument. `self` is of type `&Point`, which doesn't implement `Display`. You need to access fields: `self.x` and `self.y`.
Использование крейта cargo-expand
Более удобный способ — установить утилиту cargo-expand, которая работает со стабильной версией Rust:
cargo install cargo-expand
После установки просто запустите:
cargo expand
Вывод покажет весь развёрнутый код вашего проекта. Для приведённого примера вы увидите:
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2018::*;
#[macro_use]
extern crate std;
struct Point {
x: i32,
y: i32,
}
impl std::fmt::Display for Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Point({}, {})", self)
}
}
fn main() {
let p = Point { x: 10, y: 20 };
{
::std::io::_print(
::core::fmt::Arguments::new_v1(
&["", "\n"],
&[::core::fmt::ArgumentV1::new_display(&p)],
),
);
};
}
Теперь ошибка очевидна: в макросе impl_display! вы используете write!(f, $format, self), где $format — это строка "Point({}, {})", но передаёте только один аргумент self. Форматная строка ожидает два аргумента (для {} и {}), а вы передаёте один — структуру Point, которая не реализует Display.
Исправление макроса
Правильная версия макроса должна принимать имена полей:
macro_rules! impl_display {
($type:ty, $format:expr, $($field:ident),+) => {
impl std::fmt::Display for $type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, $format, $(self.$field),+)
}
}
};
}
struct Point {
x: i32,
y: i32,
}
impl_display!(Point, "Point({}, {})", x, y);
fn main() {
let p = Point { x: 10, y: 20 };
println!("{}", p); // Выведет: Point(10, 20)
}
Теперь макрос работает корректно, и cargo expand покажет правильный развёрнутый код.
Дополнительные советы
- Для декларативных макросов (
macro_rules!) можно использовать флаг--pretty expandedв компиляторе:rustc -Z unstable-options --pretty expanded main.rs(требуется nightly). - Для процедурных макросов (например,
#[derive(...)])cargo expand— лучший выбор, так как он показывает результат после всех этапов. - В среде разработки (например, VS Code с расширением rust-analyzer) можно навести курсор на макрос и нажать
Ctrl+Shift+I(илиCmd+Shift+Iна macOS), чтобы увидеть развёрнутый код.
Эти инструменты помогут вам отлаживать макросы и понимать, что именно генерирует ваш код. Помните, что макросы — это мощный инструмент, но их отладка требует терпения и правильных инструментов!
<<Альтернативный подход: использование trace_macros! и log_syntax!
Если вы не хотите устанавливать дополнительные инструменты или работать с nightly-версией, в Rust есть встроенные макросы для отладки декларативных макросов. Они работают на стабильной версии и могут быть полезны для быстрой проверки.
Макрос trace_macros!(true) включает трассировку всех макросов в текущей области видимости. Вывод будет в консоль при компиляции. Пример:
fn main() {
trace_macros!(true);
let p = Point { x: 10, y: 20 };
println!("{}", p);
trace_macros!(false);
}
При компиляции вы увидите:
note: trace_macro
--> src/main.rs:20:5
|
20 | println!("{}", p);
| ^^^^^^^^^^^^^^^^^
|
= note: expanding `println! { "{}" , p }`
= note: to `print! { "{}\n" , p }`
= note: expanding `print! { "{}\n" , p }`
= note: to `$crate::io::_print($crate::fmt::Arguments::new_v1(&["", "\n"], &[$crate::fmt::ArgumentV1::new_display(&p)]))`
Макрос log_syntax! выводит свои аргументы в консоль во время компиляции. Его можно вставить внутрь вашего макроса, чтобы увидеть, какие токены передаются:
macro_rules! impl_display {
($type:ty, $format:expr) => {
log_syntax!("Type:", stringify!($type), "Format:", $format);
impl std::fmt::Display for $type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
Топ ошибок начинающих PHP разработчиков: как их избежать