Vấn đề
Bạn tiến hành biên dịch, nhưng Rust thông báo rằng macro của bạn không tồn tại. Điều này thường xảy ra khi gọi các macro phổ biến như serde_json::json! hoặc một khối macro_rules! cục bộ. Vì các macro được mở rộng trước khi trình biên dịch xử lý phần còn lại của mã nguồn, chúng tuân theo các quy tắc phạm vi nghiêm ngặt hơn so với các hàm thông thường.
// Đoạn mã này gây ra lỗi
fn main() {
// Lỗi: cannot find macro `json` in this scope
let my_data = json!({ "key": "value" });
}
Các nguyên nhân thường gặp và cách khắc phục
1. Thiếu Import cho các Macro bên ngoài
Kể từ khi Rust 1.31 (Edition 2018) ra mắt, các macro hoạt động giống như các item tiêu chuẩn hơn. Bạn không còn cần phải bao bọc mọi thứ trong các khối extern crate cũ nữa. Tuy nhiên, bạn vẫn cần đưa macro vào phạm vi của tệp hiện tại.
Cách khắc phục: Sử dụng câu lệnh use rõ ràng ở đầu module của bạn. Đây là cách sạch sẽ nhất để xử lý các dependency như serde hoặc tokio.
use serde_json::json;
fn main() {
let my_data = json!({ "status": 200 }); // Hoạt động hoàn hảo
}
2. Quy tắc định nghĩa "Từ trên xuống dưới"
Rust xử lý macro_rules! theo trình tự. Nếu bạn định nghĩa một macro ở cuối tệp main.rs nhưng lại cố gắng gọi nó trong một hàm ở phía trên, trình biên dịch sẽ báo lỗi. Nó vẫn chưa đọc tới phần định nghĩa đó.
// ❌ Mã này sẽ bị lỗi
fn main() {
say_hello!();
}
macro_rules! say_hello {
() => { println!("Xin chào!"); };
}
Cách khắc phục: Di chuyển định nghĩa macro của bạn lên trên bất kỳ đoạn mã nào gọi nó. Trong Rust, thứ tự quan trọng đối với các macro trong cùng một tệp.
macro_rules! say_hello {
() => { println!("Xin chào!"); };
}
fn main() {
say_hello!(); // Đã sửa: Trình biên dịch đã thấy phần định nghĩa trước
}
3. Export Macro qua ranh giới Module
Các từ khóa hiển thị tiêu chuẩn như pub không áp dụng cho macro_rules!. Nếu bạn định nghĩa một macro trong src/utils.rs và muốn sử dụng nó trong src/main.rs, bạn cần một cách tiếp cận khác.
Cách khắc phục: Áp dụng thuộc tính #[macro_export]. Điều này thực hiện hai việc: công khai macro và đặt nó ở gốc (root) của crate.
// src/my_macros.rs
#[macro_export]
macro_rules! debug_log {
($msg:expr) => { println!("[DEBUG]: {}", $msg); };
}
// src/main.rs
mod my_macros;
fn main() {
// Truy cập thông qua crate root
crate::debug_log!("Hệ thống đã khởi tạo");
}
4. Sử dụng #[macro_use] cho các Module nội bộ
Nếu bạn không muốn sử dụng tiền tố crate:: mỗi lần, bạn có thể đưa tất cả các macro từ một module con vào phạm vi cha bằng cách sử dụng #[macro_use] trên khai báo module.
// src/main.rs
#[macro_use]
mod my_macros;
fn main() {
// Hiện có thể sử dụng mà không cần tiền tố
debug_log!("Cách này hoạt động cục bộ");
}
Kiểm tra kết quả
Kiểm tra tiến độ bằng cách chạy lệnh kiểm tra nhanh trong terminal của bạn:
cargo check
Lệnh này nhanh hơn so với việc build đầy đủ vì nó bỏ qua bước tạo mã. Nếu bạn sử dụng VS Code, extension rust-analyzer sẽ xóa các cảnh báo lỗi màu đỏ ngay khi bạn lưu tệp với câu lệnh use hoặc #[macro_export] chính xác.
Tóm tắt nhanh
- Thứ tự từ vựng: Trong cùng một tệp, hãy định nghĩa macro trước khi gọi nó.
- Tính hiển thị: Sử dụng
#[macro_export]để chia sẻ macro giữa các tệp khác nhau. - Phong cách hiện đại: Ưu tiên sử dụng
use tên_crate::tên_macrothay vì#[macro_use]kiểu cũ trên các crate bên ngoài để giữ cho namespace của bạn gọn gàng.

