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,]; // 也支持末尾逗号
这里发生了什么?
- 编译器看到
my_vec![1, 2, 3]。 - 它匹配到了模式
( $( $x:expr ),* $(,)? )。 - 它生成了如下代码:
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 用?
练习题:
- 阅读标准库中
try!宏(已废弃,现在用?)或todo!宏的源码,理解它们的实现原理。 - 尝试继续扩展
my_vec!宏,让它支持my_vec![value; count]这种重复初始化语法。
思考题:
为什么 Rust 的宏被称为 “Hygienic Macros” (卫生宏)?它与 C 语言的 #define 有什么核心区别(提示:变量作用域污染)?
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
