展开与求值语义
宏调用在模型上是什么
调用 m!(a, b) 时,编译器将 a、b 的语法片段代入宏体中 $ 占位符的位置,得到一段新的 Move 代码,再继续类型检查。
这与普通函数 f(a, b) 不同:普通函数会先对 a、b 求值(遵守 Move 的所有权与借用规则),再把值传入。
表达式代入(直观例子)
若宏定义为:
macro fun twice($e: u64): u64 { $e + $e }
则 twice!(x + 1) 展开后近似于 (x + 1) + (x + 1)(具体以编译器展开为准)。若 x + 1 有副作用或消耗性操作,语义上会与「先算一次再乘二」不同——因此宏适合纯表达式场景,或配合 lambda 明确控制求值次数。
与「先求值再调用」的对比
| 特性 | 普通 fun | macro fun |
|---|---|---|
| 实参 | 先求值为值 | 片段代入宏体 |
| 是否适合传递「未执行的代码块」 | 需额外结构包装 | 可用 lambda 参数(见下一节) |
小结
把宏理解为在类型检查之前完成的、带类型的语法变换,有助于你阅读标准库宏展开后的行为。下一节介绍 lambda 形参——只有宏才能声明的那种。