欢迎光临散文网 会员登陆 & 注册

Rust 过程宏(第二弹)

2022-12-17 14:59 作者:紧果呗  | 我要投稿

## ⚡ 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++ 的宏定义,只是简单的代码预处理程序,用宏代码替换一下部分源代码。Rust 的宏具有相当复杂的功能,更贴近编译器的语法树处理。


简单地说,Rust 宏就是内嵌的 DSL - Domain Specific Languages 可以让你可以发明自己的语法,编写出可以自行展开的代码,并且还可以实现静态反射功能。


宏的应用是符合 DRY (Don't Repeat Yourself) 软件工程原理的,有轮子的车就让它跑,没有必要重新造轮子,Don't write DRY code!


Rust 作为静态编译型语言,rustc 编译器本身由 Rust 语言实现,即实现了自举,后端部分则基于现成的 LLVM。


Rust 编译器简要工作流程如下:


  • 首先,读取源代码做 Tokens 扫描,得到 Token stream 数据,这部分程序也叫做 Syntax Analyzer;
  • 然后对源码进行词法分析得到 Abstract Syntax Tree (AST) 抽像语法树,这部分程序叫做 Parser;
  • 再将 AST 转换为 High-Level IR (HIR) 以便做类型推断、trait 接口处理以及静态类型安全性检查;
  • 再转换为 Mid-level IR (MIR) 以便做所有权借用检查和代码优化,MIR 也是 Control-Flow Graph (CFG);
  • 经过以上前端工作后,代码会转译为 LLVM IR,文件后缀一般是 .ll,是文本格式,字节码文件后缀是 .bc;
  • 得到中间代码表达,下一步就是生成相应的机器码,这就是 LLVM 要做的工作。


通过 `macro_rules` 关键字定义的一系列规则,在调用宏时,编译器会先匹配规则,匹配中的 $expansion 才被展开。


除了使用 `macro_rules` 关键字定义宏规则,Rust 有三种宏程序:


- Function-like macros - `custom!(...)` 使用 `#[proc_macro]` 定义;

- Derive macros - `#[derive(CustomDerive)]` 使用 `#[proc_macro_derive]` 定义;

- Attribute macros - `#[CustomAttribute]` 使用 `#[proc_macro_attribute]` 定义;


宏程序使用 Cargo new --lib 命令创建一个库,并且指定 proc-macro 类型,这样才能够使用过程宏:


  [lib]

  proc-macro = true


  [dependencies]

  quote = "1.0"

  syn = "1.0"

  proc-macro2 = "1.0"



宏程序与函数的区别在于后缀的感叹号:


```rust,ignore

macro_rules! $name {

  ($pattern) => {$expansion}; // $rule0 ;

  ($pattern) => {$expansion}; // $rule1 ;

  ($pattern) => {$expansion}; // ...

  ($pattern) => {$expansion}; // $ruleN ;

}

```


宏至少定义一条规则,最后一条规则的分号可省略。


列如,有以下这样一个宏定义:


  macro_rules! four {

    () => {1 + 3};

  }


调用宏时,编译器会匹配到输入为空的条件,包括 `four!()`, `four![]` or `four!{}` 这几种调用形式。

很有必要从编译器语法树构建原理的角度来解析宏的概念。


Rust 系统中有许多类型的 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: [, :, ::, ->, @, <-, …


这些会出现在代码中的 Tokens 经过编译器初步处理,就会转换成 AST - Abstract Syntax Tree,Token trees 则是介于 Tokens 与 AST 之间的东西。以树状数据结构方式存放,所以叫做语法树。


将源代码的字符流转换成 Token 是编译原理最开始的部分,做这一步工作的程序叫做 Lexical Analyzer 词法分析器,然后将源代码中字符串中的 Tokens 转换为 AST,这一步对应的程序叫做 Syntax Analyzer,即词法解析器 Parser。


编写过程宏,通常需要借助三个 crate 来解析语法树中的节点数据:


  1. syn 模块用来解析语法树(AST)的各种语法构成,即是 Syntax Analyzer。
  2. quote 解析语法树,生成 Rust 代码,从而实现想要的功能。
  3. proc_macro(std) 和 proc_macro2(3rd-party)

例如,以下是使用 `println!("{input:#?}");` 打印 `2, 2` 这个表达式对应的 TokenStream 对象:


示范:创建一个库 hello_macro_derive,并配置依赖,还有设置库类型为 proc-macro,即一个宏库:


  [lib]

  proc-macro = true


  [dependencies]

  syn = "0.14.4"

  quote = "0.6.3"


然后在 hello_macro_derive/lib.rs 文件中自定义宏的功能实现。


编写自定义宏使用的注解是 `#[proc_macro_derive(HelloMacro)]`,其中 HelloMacro 是宏的名称,使用可继承宏时,只需要使用注解 `#[derive(HelloMacro)]` 即可。


在使用时我们应该先引入这个依赖


  hello_macro_derive = { path = "../hello_macro_derive" }


然后再来使用,这里定义一个 trait 名字 HelloMacro 和方法名对应上面 quote! 宏定义的内容相匹配:

通过 HelloMacro 可继承宏,Pancakes 这个结构体便自动实现了 hello_macro() 这个接口方法。


Rust 过程宏(第二弹)的评论 (共 条)

分享到微博请遵守国家法律