Rust的宏
介绍
宏在类C语言中好处是很多的,特别是对于工程比较大的适合,业务比较复杂或者代码优美的情况下会使用。
Rust 宏非常不同于 C 里面的宏。Rust 宏会被应用于词法树(token tree),而 C 语言里的宏则是文本替换。
Rust中的宏参数可以使用 ()
、[]
以及 {},虽然三种使用形式皆可,但是 Rust 内置的宏都有自己约定俗成的使用方式,例如 vec![...]
、assert_eq!(...)
等。
在 Rust 中宏分为两大类:声明式宏( declarative macros ) macro_rules!
和三种过程宏( procedural macros ):
声明式宏(Declarative macros)使得你能够写出类似 match 表达式的东西,来操作你所提供的 Rust 代码。它使用你提供的代码来生成用于替换宏调用的代码。
过程宏(Procedural macros)允许你操作给定 Rust 代码的抽象语法树(abstract syntax tree, AST)。过程宏是从一个(或者两个)
TokenStream
到另一个TokenStream
的函数,用输出的结果来替换宏调用。#[derive]
,在之前多次见到的派生宏,可以为目标结构体或枚举派生指定的代码,例如Debug
trait类属性宏(Attribute-like macro),用于为目标添加自定义的属性
类函数宏(Function-like macro),看上去就像是函数调用
宏特点:
元编程:左右就是复用代码,类似用宏来生成代码。
宏会被展开成其它代码,且这个展开过程是发生在编译器对代码进行解释之前。因此,宏可以为指定的类型实现某个特征:先将宏展开成实现特征的代码后,再被编译。
函数就做不到这一点,因为它直到运行时才能被调用,而特征需要在编译期被实现。
宏可以引入可变参数:println!("hello"),println!("hello {}", name),而函数参数是固定的,不同JS/TS。
声明式宏
( $( $x:expr ),* )
使用圆括号
()
将整个宏模式包裹其中。紧随其后的是$()
,跟括号中模式相匹配的值(传入的 Rust 源代码)会被捕获,然后用于代码替换。在这里,模式$x:expr
会匹配任何 Rust 表达式并给予该模式一个名称:$x
。
$()
之后的逗号说明在$()
所匹配的代码的后面会有一个可选的逗号分隔符,紧随逗号之后的*
说明*
之前的模式会被匹配零次或任意多次(类似正则表达式)。
使用
vec![1, 2, 3]
来调用该宏时,$x
模式将被匹配三次,分别是1
、2
、3
。
$()
中包含的是模式$x:expr
,该模式中的expr
表示会匹配任何 Rust 表达式,并给予该模式一个名称$x
因此
$x
模式可以跟整数1
进行匹配,也可以跟字符串 "hello" 进行匹配:vec!["hello", "world"]
$()
之后的逗号,意味着1
和2
之间可以使用逗号进行分割,也意味着3
既可以没有逗号,也可以有逗号:vec![1, 2, 3,]
*
说明之前的模式可以出现零次也可以任意次,这里出现了三次
过程宏procedural macros
从形式上来看,过程宏跟函数较为相像,但过程宏是使用源代码作为输入参数,基于代码进行一系列操作后,再输出一段全新的代码。注意,过程宏中的 derive 宏输出的代码并不会替换之前的代码,这一点与声明宏有很大的不同。
属性式宏(Attribute-like macros)
派生宏(Derive macros)
函数式宏(Function-like macros)
cargo new macro_demo --lib ,在cargo.toml中新增[lib]\n proc-macro = true
[dependencies]\n syn = {version="1.0.57",features=["full","fold"]} quote = "1.0.8"
lib同级目录新建tests文件夹,下面新建attribute_macro.rs文件,然后cargo test
Rust 中的自定义派生宏能够对 trait 进行自动实现。这些宏通过使用#[derive(Trait)]
自动实现 trait。
要想在 Rust 中写一个自定义派生宏,我们可以使用DeriveInput
来解析派生宏的输入。我们还将使用proc_macro_derive
宏来定义一个自定义派生宏。
Last updated