为什么基于抽象语法树的宏比基于字符串预处理的宏好?
Why are macros based on abstract syntax trees better than macros based on string preprocessing?
我开始了我的 Rust 学习之旅。我遇到了 this line in Rust by Example:
However, unlike macros in C and other languages, Rust macros are expanded into abstract syntax trees, rather than string preprocessing, so you don't get unexpected precedence bugs.
为什么抽象语法树比字符串预处理更好?
使用 C 预处理器的一个经典示例是
#define MUL(a, b) a * b
// ...
int res = MUL(x + y, 5);
宏的使用将扩展到
int res = x + y * 5;
与预期相差甚远
int res = (x + y) * 5;
发生这种情况是因为 C 预处理器实际上只是进行简单的基于文本的替换,它并不是语言本身不可或缺的一部分。预处理和解析是两个独立的步骤。
如果预处理器改为像编译器的其余部分一样解析宏,这种情况发生在宏是实际语言语法的一部分的语言中,这不再是问题,因为优先级(如前所述)和关联性是考虑在内。
如果你在 C:
#define X(A,B) A+B
int r = X(1,2) * 3;
r
的值将是 7
,因为预处理器将其扩展为 1+2 * 3
,即 1+(2*3)
。
在 Rust 中,你会:
macro_rules! X { ($a:expr,$b:expr) => { $a+$b } }
let r = X!(1,2) * 3;
这将计算为 9
,因为编译器会将扩展解释为 (1+2)*3
。这是因为编译器知道宏的结果应该是一个完整的、自包含的表达式。
也就是说,C 宏也可以这样定义:
#define X(A,B) ((A)+(B))
这将避免任何不明显的评估问题,包括参数本身因上下文而被重新解释。但是,当您使用宏时,您永远无法确定该宏是否已正确说明了它可能使用的所有可能方式,因此很难说出任何给定的宏扩展会做什么。
通过使用 AST 节点而不是文本,Rust 确保不会发生这种歧义。
我开始了我的 Rust 学习之旅。我遇到了 this line in Rust by Example:
However, unlike macros in C and other languages, Rust macros are expanded into abstract syntax trees, rather than string preprocessing, so you don't get unexpected precedence bugs.
为什么抽象语法树比字符串预处理更好?
使用 C 预处理器的一个经典示例是
#define MUL(a, b) a * b
// ...
int res = MUL(x + y, 5);
宏的使用将扩展到
int res = x + y * 5;
与预期相差甚远
int res = (x + y) * 5;
发生这种情况是因为 C 预处理器实际上只是进行简单的基于文本的替换,它并不是语言本身不可或缺的一部分。预处理和解析是两个独立的步骤。
如果预处理器改为像编译器的其余部分一样解析宏,这种情况发生在宏是实际语言语法的一部分的语言中,这不再是问题,因为优先级(如前所述)和关联性是考虑在内。
如果你在 C:
#define X(A,B) A+B
int r = X(1,2) * 3;
r
的值将是 7
,因为预处理器将其扩展为 1+2 * 3
,即 1+(2*3)
。
在 Rust 中,你会:
macro_rules! X { ($a:expr,$b:expr) => { $a+$b } }
let r = X!(1,2) * 3;
这将计算为 9
,因为编译器会将扩展解释为 (1+2)*3
。这是因为编译器知道宏的结果应该是一个完整的、自包含的表达式。
也就是说,C 宏也可以这样定义:
#define X(A,B) ((A)+(B))
这将避免任何不明显的评估问题,包括参数本身因上下文而被重新解释。但是,当您使用宏时,您永远无法确定该宏是否已正确说明了它可能使用的所有可能方式,因此很难说出任何给定的宏扩展会做什么。
通过使用 AST 节点而不是文本,Rust 确保不会发生这种歧义。