Rust 学习笔记 26:宏入门 (Macros)

“Code that writes code.”

我们从第一天开始就在用宏:println!。 宏 (Macro) 允许我们在编译期生成代码。这被称为元编程 (Metaprogramming)

1. 宏 vs 函数

特性函数
参数数量可变参数 (println!("{}, {}", a, b))固定参数个数
调用时机编译期展开 (生成代码)运行时调用
功能可以定义 DSL,可以操作语法树只能执行普通逻辑
维护性编写复杂,调试困难易于编写和调试

2. 声明式宏 (macro_rules!)

这是 Rust 中最常见的宏类型,类似于 match 匹配。

让我们复刻一个 vec! 宏:

 1macro_rules! my_vec {
 2    // 匹配模式: ( $( $x:expr ),* )
 3    // $x:expr  -> 捕获一个表达式,命名为 x
 4    // $(...),* -> 重复匹配括号内的内容,以逗号分隔
 5    ( $( $x:expr ),* $(,)? ) => {
 6        {
 7            let mut temp_vec = Vec::new();
 8            // $(...)* -> 对每一次匹配进行重复展开
 9            $(
10                temp_vec.push($x);
11            )*
12            temp_vec
13        }
14    };
15}
16
17let v = my_vec![1, 2, 3];
18let v2 = my_vec![1, 2, 3,]; // 也支持末尾逗号

这里发生了什么?

  1. 编译器看到 my_vec![1, 2, 3]
  2. 它匹配到了模式 ( $( $x:expr ),* $(,)? )
  3. 它生成了如下代码:
    1let v = {
    2    let mut temp_vec = Vec::new();
    3    temp_vec.push(1);
    4    temp_vec.push(2);
    5    temp_vec.push(3);
    6    temp_vec
    7};
    

3. 过程宏 (Procedural Macros)

过程宏更像是一个函数:接收 TokenStream 作为输入,产生 TokenStream 作为输出。 它们通常用于:

  • 自定义 Derive#[derive(MyTrait)]
  • 属性宏#[route(GET, "/")]
  • 函数宏sql!("SELECT * FROM posts")

编写过程宏需要创建一个独立的库类型的 Crate,并且设置 proc-macro = true。这比声明式宏复杂得多。

4. 小结

第26篇笔记。 宏是 Rust 的核武器。它可以极大地减少样板代码,但也要谨慎使用,因为宏会降低代码的可读性(隐式行为)。 对于初学者,会用宏会写宏更重要。

下一篇,我们将探讨 Rust 与其他语言的交互 —— FFI (Foriegn Function Interface)。怎么在 Rust 里调用 C 代码?怎么把 Rust 编译成库给 Python 用?


练习题

  1. 阅读标准库中 try! 宏(已废弃,现在用 ?)或 todo! 宏的源码,理解它们的实现原理。
  2. 尝试继续扩展 my_vec! 宏,让它支持 my_vec![value; count] 这种重复初始化语法。

思考题

为什么 Rust 的宏被称为 “Hygienic Macros” (卫生宏)?它与 C 语言的 #define 有什么核心区别(提示:变量作用域污染)?


本文代码示例

关注公众号:极客老墨

更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。

极客老墨微信公众号二维码

相关阅读