「这是我参与2022首次更文挑战的第 13 天,活动详情查看:2022首次更文挑战」。
在本教程中,我们将介绍有关 Rust macro 的所有知识,包括 Rust macro 的介绍,并通过实例演示如何使用 Rust macro。
定义
Rust 对 macro 有很好的支持。macro 是一种你能编写出其他代码的代码,这被称为元编程(metaprogramming)。宏提供了类似于函数的功能,但没有运行时的成本。然而会有一些编译时的成本,因为宏在编译时会被扩展。
Rust宏与C语言中的宏有很大的不同,Rust的宏是应用在语法树上的,而C语言的宏只是文本替换。
宏类型
Rust有两种类型的宏:
- 声明宏
- 程序宏
声明宏使你能够编写类似于匹配表达式的东西,对你提供的作为参数的Rust代码进行操作。它使用你提供的代码来生成取代宏调用的代码。
程序宏允许你对它所提供的Rust代码的抽象语法树(AST)进行操作。其中proc宏是一个从TokenStream(或两个)到另一个TokenStream的函数,它的输出替代了宏的调用。
下面我们详细说说声明性宏和程序性宏,并探讨如何在Rust中使用宏。
声明宏
使用声明宏语法编写的宏是用 macro_rules! 声明的。声明宏的功能虽然稍显不足,但提供了一个易于使用的接口来创建宏,以消除重复的代码。其中一个常见的声明宏是 println!。声明宏提供了一个类似于匹配的接口,在匹配时,宏被替换成匹配块内的代码。
创建
// use macro_rules! <name of macro>{<Body>}
macro_rules! add {
// macth like arm for macro
($a:expr,$b:expr) => {
// macro expand to this code
{
// $a and $b will be templated using the value/variable provided to macro
$a + $b
}
};
}
fn main() {
// call to macro, $a=1 and $b=2
add!(1, 2);
}
这段代码创建了一个宏:add(1, 2) → [macro_rules!]与宏的名称: add。
这个宏实际并没有add两个数字,它只是用 expr 替换了用来添加的两个数字。宏的每个分支都需要一个函数的参数,并且可以为参数指定多种类型。如果add函数也可以只接受一个参数,我们再增加一个分支:
macro_rules! add {
// first arm match add!(1,2), add!(2,3) etc
($a:expr,$b:expr) => {{
$a + $b
}};
// Second arm macth add!(1), add!(2) etc
($a:expr) => {{
$a
}};
}
fn main() {
// call the macro
let x = 0;
add!(1, 2);
add!(x);
}
一个宏中可以有多个分支,根据不同的参数扩展到不同的代码。每个分支可以接受多个参数,以 $ 符号开始,后面是一个标记类型。类型如下:
- item ⇒ 如函数、结构体、模块等;
- block ⇒ 即语句块或表达式,用大括号包围;
- stmt ⇒ 语句
- pat ⇒ 模式
- expr ⇒ 表达式
- ty ⇒ 类型
- ident ⇒ 标识符
- path ⇒ 路径(例如:
foo,::std::mem::replace,transmute::<_, int>, ...) - meta ⇒
#[...]和#![...]属性中的文本内容 - tt ⇒ 单一的
token tree - vis ⇒ 一个可能为空的可见性限定词
在这个例子中,我们使用 $typ 参数代表 标记类型:ty 作为数据类型,如u8、u16等。这个宏在add数字之前会转换为一个特定的类型:
macro_rules! add_as {
// using a ty token type for macthing datatypes passed to maccro
($a:expr,$b:expr,$typ:ty) => {
$a as $typ + $b as $typ
};
}
fn main() {
println!("{}", add_as!(0, 2, u8));
}