宏、unsafe 与 FFI advanced 30 分钟 更新 2026-06-15

声明宏 macro_rules!

用 macro_rules! 在编译期生成重复代码。

声明宏 macro_rules!

你会学到什么

  • macro_rules! 通过“模式 => 展开”的规则生成代码。
  • 元变量如 $x:expr$t:ty 捕获语法片段。
  • $(...),* 表示重复,可处理任意数量的参数。

最小示例

macro_rules! my_max {
    ($a:expr, $b:expr) => {
        if $a >= $b { $a } else { $b }
    };
}

运行代码

cd examples
cargo run -p rt_39_macros
cargo test -p rt_39_macros

代码讲解

宏在编译期展开,所以 my_max!(3, 7) 直接变成一个 if 表达式,没有函数调用开销,也不限制参数类型。

重复用 $(...)* 表达,下面的宏接受任意多个 key => value:

macro_rules! hashmap {
    ($($key:expr => $value:expr),* $(,)?) => {{
        let mut map = std::collections::HashMap::new();
        $( map.insert($key, $value); )*
        map
    }};
}

$(,)? 允许结尾多写一个逗号。常见元变量:expr(表达式)、ident(标识符)、ty(类型)、tt(token 树)。

常见错误

宏卫生(hygiene)问题:在宏里引入的变量名可能和调用处冲突。把内部变量包在 {{ ... }} 块里、用不易撞名的名字,可减少意外。

练习

  • 写一个 vec_of_strings!["a", "b"] 宏,生成 Vec<String>
  • my_max! 扩展成支持 3 个及以上参数(提示:递归调用宏)。

小结

macro_rules! 用模式匹配生成代码,擅长消除重复和构造字面量,是声明宏的主力。

下一步

需要绕过借用检查器做底层操作时,要用 unsafe。下一章学习 unsafe 基础。

完整示例代码

下面是 examples/39_macros/ 的完整源码。无需 clone 仓库,直接在页面上阅读、复制、对照运行。

examples/39_macros/src/main.rs
//! 声明宏 macro_rules!:在编译期生成代码。

/// 取两个值中较大者(演示最简单的宏)。
macro_rules! my_max {
    ($a:expr, $b:expr) => {
        if $a >= $b { $a } else { $b }
    };
}

/// 用 `key => value` 字面量构造 HashMap,支持任意个数和结尾逗号。
macro_rules! hashmap {
    ($($key:expr => $value:expr),* $(,)?) => {{
        #[allow(unused_mut)]
        let mut map = std::collections::HashMap::new();
        $(
            map.insert($key, $value);
        )*
        map
    }};
}

fn main() {
    println!("my_max = {}", my_max!(3, 7));

    let scores = hashmap! {
        "alice" => 90,
        "bob" => 85,
    };
    println!("alice = {:?}", scores.get("alice"));
}

#[cfg(test)]
mod tests {
    #[test]
    fn max_macro() {
        assert_eq!(my_max!(3, 7), 7);
        assert_eq!(my_max!(9, 2), 9);
    }

    #[test]
    fn hashmap_macro() {
        let map = hashmap! {
            "a" => 1,
            "b" => 2,
        };
        assert_eq!(map.get("a"), Some(&1));
        assert_eq!(map.len(), 2);
    }

    #[test]
    fn hashmap_macro_empty() {
        let map: std::collections::HashMap<&str, i32> = hashmap! {};
        assert!(map.is_empty());
    }
}
examples/39_macros/Cargo.toml
[package]
name = "rt_39_macros"
version.workspace = true
edition.workspace = true
publish.workspace = true