Rust 過程宏(第二彈)

## ? Rust Macros 宏編程
- [The Little Book of Rust Macros](https://veykril.github.io/tlborm/)
- [The Little Book of Rust Macros](https://danielkeep.github.io/tlborm/book/index.html)
- [Introduction to Procedural Macros in Rust](https://tinkering.xyz/introduction-to-proc-macros/)
- [Procedural Macros in Rust 2018](https://blog.rust-lang.org/2018/12/21/Procedural-Macros-in-Rust-2018.html)
- [3.2. Procedural Macros](https://doc.rust-lang.org/reference/procedural-macros.html)
- https://doc.rust-lang.org/rust-by-example/macros.html
- https://doc.rust-lang.org/book/ch19-06-macros.html
- https://doc.rust-lang.org/stable/reference/attributes.html#active-and-inert-attributes
- https://docs.rs/json/0.12.4/json/
- https://github.com/Jeangowhy/Godot-Tour
首先,不像 C/C++ 的宏定義,只是簡單的代碼預(yù)處理程序,用宏代碼替換一下部分源代碼。Rust 的宏具有相當(dāng)復(fù)雜的功能,更貼近編譯器的語法樹處理。
簡單地說,Rust 宏就是內(nèi)嵌的 DSL - Domain Specific Languages 可以讓你可以發(fā)明自己的語法,編寫出可以自行展開的代碼,并且還可以實現(xiàn)靜態(tài)反射功能。
宏的應(yīng)用是符合 DRY (Don't Repeat Yourself) 軟件工程原理的,有輪子的車就讓它跑,沒有必要重新造輪子,Don't write DRY code!
Rust 作為靜態(tài)編譯型語言,rustc 編譯器本身由 Rust 語言實現(xiàn),即實現(xiàn)了自舉,后端部分則基于現(xiàn)成的 LLVM。
Rust 編譯器簡要工作流程如下:
- 首先,讀取源代碼做 Tokens 掃描,得到 Token stream 數(shù)據(jù),這部分程序也叫做 Syntax Analyzer;
- 然后對源碼進(jìn)行詞法分析得到 Abstract Syntax Tree (AST) 抽像語法樹,這部分程序叫做 Parser;
- 再將 AST 轉(zhuǎn)換為 High-Level IR (HIR) 以便做類型推斷、trait 接口處理以及靜態(tài)類型安全性檢查;
- 再轉(zhuǎn)換為 Mid-level IR (MIR) 以便做所有權(quán)借用檢查和代碼優(yōu)化,MIR 也是 Control-Flow Graph (CFG);
- 經(jīng)過以上前端工作后,代碼會轉(zhuǎn)譯為 LLVM IR,文件后綴一般是 .ll,是文本格式,字節(jié)碼文件后綴是 .bc;
- 得到中間代碼表達(dá),下一步就是生成相應(yīng)的機器碼,這就是 LLVM 要做的工作。
通過 `macro_rules` 關(guān)鍵字定義的一系列規(guī)則,在調(diào)用宏時,編譯器會先匹配規(guī)則,匹配中的 $expansion 才被展開。
除了使用 `macro_rules` 關(guān)鍵字定義宏規(guī)則,Rust 有三種宏程序:
- Function-like macros - `custom!(...)` 使用 `#[proc_macro]` 定義;
- Derive macros - `#[derive(CustomDerive)]` 使用 `#[proc_macro_derive]` 定義;
- Attribute macros - `#[CustomAttribute]` 使用 `#[proc_macro_attribute]` 定義;
宏程序使用 Cargo new --lib 命令創(chuàng)建一個庫,并且指定 proc-macro 類型,這樣才能夠使用過程宏:
??[lib]
??proc-macro = true
??[dependencies]
??quote = "1.0"
??syn = "1.0"
??proc-macro2 = "1.0"
宏程序與函數(shù)的區(qū)別在于后綴的感嘆號:
```rust,ignore
macro_rules! $name {
??($pattern) => {$expansion}; // $rule0 ;
??($pattern) => {$expansion}; // $rule1 ;
??($pattern) => {$expansion}; // ...
??($pattern) => {$expansion}; // $ruleN ;
}
```
宏至少定義一條規(guī)則,最后一條規(guī)則的分號可省略。
列如,有以下這樣一個宏定義:
??macro_rules! four {
????() => {1 + 3};
??}
調(diào)用宏時,編譯器會匹配到輸入為空的條件,包括 `four!()`, `four![]` or `four!{}` 這幾種調(diào)用形式。
很有必要從編譯器語法樹構(gòu)建原理的角度來解析宏的概念。
Rust 系統(tǒng)中有許多類型的 Tokens:
- Identifiers: foo, Bambous, self, we_can_dance, LaCaravane, …
- Integers: 42, 72u32, 0_______0, …
- Keywords: _, fn, self, match, yield, macro, …
- Lifetimes: 'a, 'b, 'a_rare_long_lifetime_name, …
- Strings: "", "Leicester", r##"venezuelan beaver"##, …
- Symbols: [, :, ::, ->, @, <-, …
這些會出現(xiàn)在代碼中的 Tokens 經(jīng)過編譯器初步處理,就會轉(zhuǎn)換成 AST - Abstract Syntax Tree,Token trees 則是介于 Tokens 與 AST 之間的東西。以樹狀數(shù)據(jù)結(jié)構(gòu)方式存放,所以叫做語法樹。
將源代碼的字符流轉(zhuǎn)換成 Token 是編譯原理最開始的部分,做這一步工作的程序叫做 Lexical Analyzer 詞法分析器,然后將源代碼中字符串中的 Tokens 轉(zhuǎn)換為 AST,這一步對應(yīng)的程序叫做 Syntax Analyzer,即詞法解析器 Parser。
編寫過程宏,通常需要借助三個 crate 來解析語法樹中的節(jié)點數(shù)據(jù):
- syn 模塊用來解析語法樹(AST)的各種語法構(gòu)成,即是 Syntax Analyzer。
- quote 解析語法樹,生成 Rust 代碼,從而實現(xiàn)想要的功能。
- proc_macro(std) 和 proc_macro2(3rd-party)
例如,以下是使用 `println!("{input:#?}");` 打印 `2, 2` 這個表達(dá)式對應(yīng)的 TokenStream 對象:

示范:創(chuàng)建一個庫 hello_macro_derive,并配置依賴,還有設(shè)置庫類型為 proc-macro,即一個宏庫:
??[lib]
??proc-macro = true
??[dependencies]
??syn = "0.14.4"
??quote = "0.6.3"
然后在 hello_macro_derive/lib.rs 文件中自定義宏的功能實現(xiàn)。

編寫自定義宏使用的注解是 `#[proc_macro_derive(HelloMacro)]`,其中 HelloMacro 是宏的名稱,使用可繼承宏時,只需要使用注解 `#[derive(HelloMacro)]` 即可。
在使用時我們應(yīng)該先引入這個依賴
??hello_macro_derive = { path = "../hello_macro_derive" }
然后再來使用,這里定義一個 trait 名字 HelloMacro 和方法名對應(yīng)上面 quote! 宏定義的內(nèi)容相匹配:

通過 HelloMacro 可繼承宏,Pancakes 這個結(jié)構(gòu)體便自動實現(xiàn)了 hello_macro() 這個接口方法。