欢迎大家来到IT世界,在知识的湖畔探索吧!
宏macro
宏在Rust里是指一组相关特性的集合称谓:
- 使用macro_rules!构建的声明宏(declarative macro)
- 3种过程宏
- 自定义#[derive]宏,用于struct或enum,可以为其指定随derive属性添加的代码
- 类似属性的宏,可以在任意条目上添加自定义属性
- 类似于函数的宏,看起来像函数调用,对其指定为参数的token进行操作
macro_rules! 中有一些奇怪的地方。在将来,会有第二种采用 macro 关键字的声明宏,其工作方式类似但修复了这些极端情况。在此之后,macro_rules! 实际上就过时(deprecated)了。在此基础之上,同时鉴于大多数 Rust 程序员 使用 宏而非 编写 宏的事实,此处不再深入探讨 macro_rules!。请查阅在线文档或其他资源,如 “The Little Book of Rust Macros(https://danielkeep.github.io/tlborm/book/index.html)” 来更多地了解如何写宏。
函数与宏的差别
从本质上看,宏是用来编写可以生成其它代码的代码(元编程,metaprograming)。 函数在定义签名时,必须声明参数的个数和类型,而宏可以处理可变的参数。编译器会在解释代码前展开宏。宏的定义比函数复杂得多,难以阅读、理解和维护。在某个文件调用宏时,必须提前定义宏或将宏引入当前作用域。而函数可以在任何位置定义并在任何位置使用。
基于属性来生成代码的过程宏
这种形式更像函数(某种形式的过程)一些。它会接收并操作输入的Rust代码,并生成另外一些Rust代码作为结果。
三种过程宏:
- 自定义派生
- 属性宏
- 函数宏
创建过程宏时,宏定义必须单独放在它们自己的包中,并且使用特殊的包类型。例:
//src/lib.rs
use proc_macro;
#[some_attribute]//some_attribute 是一个使用特定宏的占位符。
pub fn some_name(input: TokenStream) -> TokenStream {//TokenStream 类型由包含在 Rust 中的 proc_macro crate 定义并表示token序列。 这是宏的核心:宏所操作的源代码构成了输入 TokenStream,宏产生的代码是输出 TokenStream。
}
欢迎大家来到IT世界,在知识的湖畔探索吧!
自定义derive宏(派生宏)
需求:
- 创建一个hello_macro library包,定义一个拥有关联函数hello_macro的HelloMacro trait
- 我们定义一个能自动实现该trait的过程宏
- 只需要在对应的类型上标准#[derive(HelloMacro)],就能得到hello_macro函数的默认实现
首先新建并打开文件夹macro_demo,然后新建一个Cargo.toml文件,定义工作空间,内容如下
欢迎大家来到IT世界,在知识的湖畔探索吧![workspace]
members = [
"hello_macro",
"hello_macro_derive",
"pancakes",
]
在macro_demo目录下下分别用命令行执行
> cargo new hello_macro --lib
> cargo new hello_macro_derive --lib
> cargo new pancakes
其中hello_macro是定义HelloMacro trait的包,hello_macro_derive是定义派生宏的包,pancakes是使用派生宏的包。
在hello_macro的lib.rs里定义HelloMacro trait
欢迎大家来到IT世界,在知识的湖畔探索吧!pub trait HelloMacro {
fn hello_macro();
}
修改hello_macro_derive的Cargo.toml如下,其中两个依赖是用来解析和生成TokenStream的。
[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
syn = "1.0.84"
quote = "1.0.14"
在hello_macro_derive里的lib.rs里创建派生宏如下
extern crate proc_macro;
use crate::proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(HelloMacro)]
pub fn proc_macro_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}", stringify!(#name));
}
}
};
gen.into()
}
其中proc_macro是编译器用来读取和操作我们 Rust 代码的 API;syn crate 将字符串中的 Rust 代码解析成为一个可以操作的数据结构;quote 则将 syn 解析的数据结构转换回 Rust 代码。 首先调用syn::parse(input).unwrap()将输入的代码解析为抽象语法树,它的部分结构如下:
DeriveInput {
// --snip--
ident: Ident {
ident: "Pancakes",
span: #0 bytes(95..103)
},
data: Struct(
DataStruct {
struct_token: Struct,
fields: Unit,
semi_token: Some(
Semi
)
}
)
}
以上是当我们有一个自定义结构体Pancakes,并在上面标注我们定义的派生宏时解析成的。
然后定义了一个方法impl_hello_macro,读取抽象语法树并生成我们需要的代码。&ast.ident就是被标注该宏的结构体的名字。 quote! 宏让我们可以编写希望返回的 Rust 代码。quote! 宏执行的直接结果并不是编译器所期望的并需要转换为 TokenStream。为此需要调用 into 方法,它会消费这个中间表示(intermediate representation,IR)并返回所需的 TokenStream 类型值。
这个宏也提供了一些非常酷的模板机制;我们可以写 #name ,然后 quote! 会以名为 name 的变量值来替换它。你甚至可以做一些类似常用宏那样的重复代码的工作。 我们期望我们的过程式宏能够为通过 #name 获取到的用户注解类型生成 HelloMacro trait 的实现。该 trait 的实现有一个函数 hello_macro ,其函数体包括了我们期望提供的功能:打印 Hello, Macro! My name is 和注解的类型名。
接着在pancakes包的Cargo.toml里添加我们创建的前两个包为依赖:
[package]
name = "pancakes"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hello_macro = {path = "../hello_macro"}
hello_macro_derive = {path = "../hello_macro_derive"}
最后在其main.rs里使用自定义的派生宏:
use hello_macro_derive::HelloMacro;
use hello_macro::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes{}
fn main() {
Pancakes::hello_macro();
}
运行cargo run,会打印Hello, Macro! My name is Pancakes。可以看出我们自定义的宏已经为结构体Pancakes自动实现了HelloMacro trait的关联函数hello_macro。
属性宏
属性宏与自定义derive宏(派生宏)类型:
- 允许创建新的属性
- 但是不为derive属性生成代码
属性宏更加灵活:
- derive宏只能用于结构体和枚举
- 属性宏可以用于任意条目,如函数
例子,可以创建一个名为 route 的属性用于注解 web 应用程序框架(web application framework)的函数:
#[route(GET, "/")]
fn index() {}
其宏定义的函数签名看起来像这样:
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {}
其中attr是(GET, “/”),item对应index函数。
类属性宏与自定义派生宏工作方式一致:创建 proc-macro crate 类型的包并实现希望生成代码的函数!
函数宏
函数宏是类似于函数调用的宏,但它比普通函数更加灵活。函数宏可以接收TokenStream作为参数。与上面两种宏一样,在定义中使用Rust代码来操作TokenStream,例如我们想定义一个解析SQL语句的宏:
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
上面的宏就可以这么使用sql!()
let sql = sql!(SELECT * FROM posts WHERE id=1);
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/17627.html