为什么基于抽象语法树的宏比基于字符串预处理的宏更好?

时间:2018-05-24 14:39:13

标签: macros rust rust-macros

我开始学习Rust了。我遇到了this line in Rust by Example

  

但是,与C和其他语言中的宏不同,Rust宏被扩展为抽象语法树,而不是字符串预处理,因此您不会遇到意外的优先级错误。

为什么抽象语法树比字符串预处理更好?

2 个答案:

答案 0 :(得分:11)

如果你在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确保不会发生这种歧义。

答案 1 :(得分:4)

使用C预处理器的经典​​示例是

#define MUL(a, b) a * b

// ...

int res = MUL(x + y, 5);

宏的使用将扩展为

int res = x + y * 5;

离预期的

非常远
int res = (x + y) * 5;

这是因为C预处理器实际上只是简单的基于文本的替换,它实际上不是语言本身不可或缺的一部分。预处理和解析是两个单独的步骤。

如果预处理器改为像编译器的其余部分那样解析宏,这对于宏是实际语言语法的一部分的语言来说就是这种情况,这就不再是一个问题了,因为优先级(如上所述)和关联性之类的东西被用于帐户。