Rust
语言支持宏,如我们之前使用的 assert_eq!
,println!
等。宏做了函数不能做的一些事情,例如,assert_eq!
当一个断言失败时,assert_eq!
生成包含断言的文件名和行号的错误消息,普通函数无法获取这些信息,但宏可以,因为它们的工作方式完全不同。
宏是一种简写,在编译期间,在检查类型和生成任何机器代码之前,每个宏调用都会被扩展。也就是说,它被一些 Rust
代码替换。assert_eq!
调用扩展为大致如下:
1 2 3 4 5 6 7 8 9 10
| match (&gcd(6, 10), &2) { (left_val, right_val) => { if !(*left_val == *right_val) { panic!( "assertion failed: `(left == right)`, (left: `{:?}`, right: `{:?}`)", left_val, right_val ); } } }
|
panic!
也是一个宏,它本身扩展为更多的 Rust
代码。该代码使用到了另外两个宏:file!()
和 line!()
。 一旦 crate
中的每个宏调用都被完全展开,Rust
就会进入下一个编译阶段。
在运行时,断言失败看起来像这样:
thread 'main' panicked at 'assertion failed: `(left == right)`, (left: `17`, right: `2`)', gcd.rs:7
如果熟悉 C++
,可能对宏有过一些不好的体验。但是 Rust
宏采用不同的方法,类似于 Scheme
的语法规则。与 C++
宏相比,Rust
宏可以更好地与语言的其余部分集成,因此更不容易出错。宏调用总是标有感叹号 !
,因此在阅读代码时它们会比较突出,所以不会意外调用它们。Rust
宏从不插入不匹配的括号或圆括号,并且 Rust
宏带有模式匹配,使得编写既可维护又易于使用的宏变得更加容易。
在本节中,我们将通过几个简单的例子来展示如何编写宏。但与 Rust
的大部分内容一样,理解宏需要下很大功夫。在这里将介绍一个很复杂的宏的设计,它可以将 JSON
文字直接嵌入到我们的程序中。但是宏的内容涵盖的非常多,因此这里将提供一些进一步研究的建议,包括我们在此处展示的高级技术,以及称为过程宏的更强大的工具。