-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Rust宏设计
从最常见到的vec![]到substrait中各种宏来帮助用户完成pallet基本功能
#[pallet::storage]
pub(super) type Something<T: Config> = StorageValue<_, _>;Rust中的宏,最重要的是帮助开发者少写boilerplate代码,快速且心智简单地完成基础设置和基本功能,从而让开发者更专注在业务逻辑上。
Rust中的宏,大致可分为
- “声明宏”(Declare Macros)
- “过程宏”(Procedural Macros)
最开始学习rust时,熟悉的两个语句:
let mut v = vec![];中的做Vec类型构建的宏,就是 声明宏#[derive(Debug)]Struct MyStruct { ... },自动给MyStruct添加Debug trait实现,这里就是过程宏的一种(derived macro)
如果要编写自己的宏,或者深入看懂其他的宏,就需要学习Rust中的宏,设计和实现,所幸的是,大神[dtolnay](https://github.com/dtolnay)为我们搞了一个学习路径 - proc-macro-workshop,认真完成这里的多个任务/关卡,之后你就可以说对于Rust Macro的编写就达到了中等程度
我目前做完了builder和customDebug,如下是我的一些思路和线索,大概的方向对了,剩下的是细节。
-
rust宏编写,基本上是在跟ST- syntax tree打交道,其被包装程度介于字符和AST之间;当宏内的语句被解析为DeriveInput,你可以去看它的各个成员变量,并顺着跟下去,或者打印出来,你就会发现有一些字符已经被包装为抽象的节点,比如一个
Ident结构,但也会看到类似-,>这样的字符(Punct),编写宏,就是要跟这些东西打交道,添加,修改,删除,做出一个新的TokenStream从宏观角度看,rust宏处理就是把一个输入的ST,经过添删改等处理,变成一个新的ST,并返回给编译器做后续处理。
input ST ---> procedural macro ---> Ok(new_st) |--->Err(compile error) -
以derive宏处理为例,有如下的流程:

在宏处理过程中,有Syn crate,提供parse等相关函数,把Token Stream,转成ST数据类型,如常见的标识符Ident,符号:: Colon2, 等等各种数据结构,有Quote crate,再把这些数据类型转换回TokenStream
-
宏编写必须掌握的四个crate
- syn
- quote
- proc_macro2
- proc_macro
syn和quote在上图中已经看到;
proc_macro中主要是定义TokenStream结构,出现时间最早,编译器依赖它;
proc_macro2出现时间晚于proc_macro,proc_macro2提供了更多的结构和功能,syn,quote都与之配合。
proc_macro2还提供了TokenTree等,这些都会在declare macro中被使用到。
proc_macro2::TokenStream具有From/Into trait到proc_macro::TokenStream一般的开发套路是:
- 流程中起始传入,和最后输出的TokenStream是
proc_macro::TokenStream; - 中间过程都使用
proc_macro2::TokenStream,最后在使用into()转成proc_macro::TokenStream