编译过程
整体流程:[源代码]->分词->[Tokens词条流]->解析->[AST]->语法分析,宏扩展→[高级中间语言HIR]->类型检查->[中级中间语言MIR]->转换->[LLVM IR]->LLVM->[目标文件]->链接->[可执行程序]
详细过程如下:
- 解析输入:将
.rs
文件作为输入并进行解析生成AST
(抽象语法树); - 名称解析,宏扩展和属性配置:解析完毕后处理
AST
,处理#[cfg]
节点解析路径,扩展宏; - 转为HIR:名称解析完毕后将AST转换为
HIR
(高级中间表示),HIR比AST处理的更多,但是他不负责解析Rust的语法,例如((1+2)+3)
和1+2+3
在AST中会保留括号,虽然两者的含义相同但是会被解析成不同的树,但是在HIR中括号节点将会被删除,这两个表达式会以相同的方式表达; - 类型检查以及后续分析:处理HIR的重要步骤就是类型检查,例如使用
x.f
时如果我们不知道x
的类型就无法判断访问的哪个f
字段,类型检查会创建TypeckTables
其中包括表达式的类型,方法的解析方式; - 转为MIR以及后置处理:完成类型检查后,将HIR转为MIR(中级中间表示)进行借用检查以及优化;
- 转为LLVM IR和优化:LLVM进行优化,从而生成许多
.o
文件; - 链接: 最后将那些
.o
文件链接在一起。
Rust在预处理时,会加入以下代码:
1 |
|
从而自动导入标准库。
声明宏(Declarative Macro)
宏解析器将宏扩展的时机在解析过程中。
声明宏中可以捕获的类型:
- item,语言项,比如模块、声明、函数定义、类型定义、结构体定义、impl实现等。
- block,代码块,由花括号限定的代码;
- stmt,语句,一般是指以分号结尾的代码;
- expr,表达式,会生成具体的值;
- pat,模式;
- ty,类型;
- ident,标识符;
- path,路径,比如foo、std::iter等;
- meta,元信息,包含在#[…]或者#![…]属性内的信息;
- tt,TokenTree的缩写,词条树;
- vis,可见性,比如pub;
- lifetime,生命周期参数。
词法树的范围比表达式的范围广,比如匹配一个语句块时,就必须用tt。
重复匹配的模式是“$(…) sep rep”,具体的说明如下:
- $(…),代表要把重复匹配的模式置于其中;
- sep,代表分隔符,常用逗号、分号和=>。这个分隔符可以依据具体的情况忽略。
- rep,代表控制重复次数的标记,目前支持两种:* 和 +,代表“重复零次及以上”和“重复一次及以上”。
展开宏:
1 | cargo rustc -- -Z unstable-options --pretty=expanded |
或
1 | rustc -Z unstable-options --pretty=expanded main.rs |
1 |
|
hashmap实现
递归调用
1 |
|
第一层转换为hashmap ! ("a" => 1, "b" => 2, "c" => 3)
。
重复匹配规则
1 | macro_rules! hashmap { |
预分配空间
1 | macro_rules! unit { |
消除外部宏
1 |
|
#![feature(trace_macros)]
在nightly版本下可以跟踪宏展开,在需要展开宏的地方使用trace_macros!(true);
打开跟踪。
导入/导出
#[macro_export]
表示下面的宏定义对其他包也是可见的。#[macro_use]
可以导入宏。
在宏定义中使用$crate
,可以在被导出时,让编译器根据上下文推断包名,避免依赖问题。
过程宏
过程宏的Cargo.toml中,设置lib类型:
1 | [lib] |
自定义派生属性
derive属性,自动为结构体或枚举类型进行语法扩展。可以使用TDD(测试驱动开发)的方式来开发。
包结构:
1 | |-Cargo.toml |
先写test.rs
1 |
|
再写lib.rs
1 | extern crate proc_macro; |
自定义属性
可以说自定义派生属性是自定义属性的特例。例如条件编译属性#[cfg()]
和测试属性#[test]
都是自定义属性。
test.rs
1 | use my_proc_macro::attr_with_args; |
lib.rs
1 | extern crate proc_macro; |
在引号的括号要用{{ {} }}
转义,最内部的{}
是给args
占位的。
Bang宏/类函数宏
test.rs
1 |
|
lib.rs
1 |
|
第三方包
使用syn
,quote
和proc_macro
可以实现自定义派生属性功能。
以下代码实现了派生属性New
。
1 | extern crate proc_macro; |