Jenner's Blog

不变秃,也要变强!

0%

Rust之宏入门

编译过程

整体流程:[源代码]->分词->[Tokens词条流]->解析->[AST]->语法分析,宏扩展→[高级中间语言HIR]->类型检查->[中级中间语言MIR]->转换->[LLVM IR]->LLVM->[目标文件]->链接->[可执行程序]

详细过程如下:

  1. 解析输入:将.rs文件作为输入并进行解析生成AST(抽象语法树);
  2. 名称解析,宏扩展和属性配置:解析完毕后处理AST,处理#[cfg]节点解析路径,扩展宏;
  3. 转为HIR:名称解析完毕后将AST转换为HIR(高级中间表示),HIR比AST处理的更多,但是他不负责解析Rust的语法,例如((1+2)+3)1+2+3在AST中会保留括号,虽然两者的含义相同但是会被解析成不同的树,但是在HIR中括号节点将会被删除,这两个表达式会以相同的方式表达;
  4. 类型检查以及后续分析:处理HIR的重要步骤就是类型检查,例如使用x.f时如果我们不知道x的类型就无法判断访问的哪个f字段,类型检查会创建TypeckTables其中包括表达式的类型,方法的解析方式;
  5. 转为MIR以及后置处理:完成类型检查后,将HIR转为MIR(中级中间表示)进行借用检查以及优化;
  6. 转为LLVM IR和优化:LLVM进行优化,从而生成许多.o文件;
  7. 链接: 最后将那些.o文件链接在一起。

Rust在预处理时,会加入以下代码:

1
2
3
4
5
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;

从而自动导入标准库。

声明宏(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
2
3
4
5
6
7
8
9
10
11
12
13
14
#[macro_export]
macro_rules! my_vec {
// x 为重复匹配到的表达式,“,”可以根据情况忽略
($($x: expr), *) => {
{
let mut temp_vec = Vec::new();
// 重复匹配到的值在这里访问
$(
temp_vec.push($x);
)*
temp_vec
}
};
}

hashmap实现

递归调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#![allow(unused)]
macro_rules! hashmap {
// 利用递归调用消去最后键值对的结尾逗号
// 如果是末尾仍有逗号的,会转换成($($key:expr => $value:expr),*)
($($key:expr => $value:expr,)*) =>
{ hashmap!($($key => $value),*) };
($($key:expr => $value:expr),* ) => {
{
let mut _map = ::std::collections::HashMap::new();
$(
_map.insert($key, $value);
)*
_map
}
};
}
fn main(){
let map = hashmap!{
"a" => 1,
"b" => 2,
"c" => 3,
};
assert_eq!(map["a"], 1);
}

第一层转换为hashmap ! ("a" => 1, "b" => 2, "c" => 3)

重复匹配规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)*) => {
{
let mut _map = ::std::collections::HashMap::new();
$(
_map.insert($key, $value);
)*
_map
}
};
}
fn main(){
let map = hashmap!{
"a" => 1,
"b" => 2,
"c" => 3,
};
assert_eq!(map["a"], 1);
}

预分配空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
macro_rules! unit {
($($x:tt)*) => (());
}
macro_rules! count {
($($key:expr),*) => (<[()]>::len(&[$(unit!($key)),*]));
}
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)*) => {
{
let _cap = count!($($key),*);
let mut _map
= ::std::collections::HashMap::with_capacity(_cap);
$(
_map.insert($key, $value);
)*
_map
}
};
}

消除外部宏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#![feature(trace_macros)]
macro_rules! hashmap {
(@unit $($x:tt)*) => (());
(@count $($rest:expr),*) =>
(<[()]>::len(&[$(hashmap!(@unit $rest)),*]));
($($key:expr => $value:expr),* $(,)*) => {
{
let _cap = hashmap!(@count $($key),*);
let mut _map =
::std::collections::HashMap::with_capacity(_cap);
$(
_map.insert($key, $value);
)*
_map
}
};
}
fn main(){
trace_macros!(true);
let map = hashmap!{
"a" => 1,
"b" => 2,
"c" => 3,
};
assert_eq!(map["a"], 1);
}

#![feature(trace_macros)]在nightly版本下可以跟踪宏展开,在需要展开宏的地方使用trace_macros!(true);打开跟踪。

导入/导出

#[macro_export]表示下面的宏定义对其他包也是可见的。#[macro_use]可以导入宏。

在宏定义中使用$crate,可以在被导出时,让编译器根据上下文推断包名,避免依赖问题。

过程宏

过程宏的Cargo.toml中,设置lib类型:

1
2
[lib]
proc_macro = true

自定义派生属性

derive属性,自动为结构体或枚举类型进行语法扩展。可以使用TDD(测试驱动开发)的方式来开发。

包结构:

1
2
3
4
5
|-Cargo.toml
|-src
|- lib.rs
|-tests
|- test.rs

先写test.rs

1
2
3
4
5
6
7
8
9
#[macro_use]
extern crate my_proc_macro;

#[derive(A)]
struct A;
#[test]
fn test_derive_a(){
assert_eq!("hello from impl A".to_string(), A.a());
}

再写lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extern crate proc_macro;
use self::proc_macro::TokenStream;

// 自定义派生属性
#[proc_macro_derive(A)]
pub fn derive(input: TokenStream) -> TokenStream {
let _input = input.to_string();
assert!(_input.contains("struct A;"));
r#"
impl A {
fn a(&self) -> String{
format!("hello from impl A")
}
}
"#.parse().unwrap()
}

自定义属性

可以说自定义派生属性是自定义属性的特例。例如条件编译属性#[cfg()]和测试属性#[test]都是自定义属性。

test.rs

1
2
3
4
5
6
7
use my_proc_macro::attr_with_args;
#[attr_with_args("Hello, Rust!")]
fn foo(){}
#[test]
fn test_foo(){
assert_eq!(foo(), "Hello, Rust!");
}

lib.rs

1
2
3
4
5
6
7
8
9
10
11
extern crate proc_macro;
use self::proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn attr_with_args(args: TokenStream, input: TokenStream)
-> TokenStream {
let args = args.to_string();
let _input = input.to_string();
format!("fn foo() -> &'static str {% raw %}{{ {} }}{% endraw %}", args)
.parse().unwrap()
}

在引号的括号要用{{ {} }}转义,最内部的{}是给args占位的。

Bang宏/类函数宏

test.rs

1
2
3
4
5
6
7
8
9
#![feature(proc_macro_hygiene)]
use my_proc_macro::hashmap;
#[test]
fn test_hashmap(){
let hm = hashmap!{ "a":1,"b":2,};
assert_eq!(hm["a"],1);
let hm = hashmap!{"a"=>1,"b"=>2,"c"=>3};
assert_eq!(hm["c"],3);
}

lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#[proc_macro]
pub fn hashmap(input: TokenStream) -> TokenStream {
// 转换input为字符串
let _input = input.to_string();
// 将input字符串结尾的逗号去掉,否则在下面迭代中将报错
let input = _input.trim_end_matches(',');
// 用split将字符串分割为slice,然后用map去处理
// 为了支持「"a" : 1」或 「"a" => 1」这样的语法
let input: Vec<String> = input.split(",").map(|n| {
let mut data = if n.contains(":") { n.split(":") }
else { n.split(" => ") };
let (key, value) =
(data.next().unwrap(), data.next().unwrap());
format!("hm.insert({}, {})", key, value)
}).collect();
let count: usize = input.len();
let tokens = format!("
{% raw %}{{
let mut hm =
::std::collections::HashMap::with_capacity({});
{}
hm
}}{% endraw %}", count,
input.iter().map(|n| format!("{};", n)).collect::<String>()
);
// parse函数会将字符串转为Result<TokenStream>
tokens.parse().unwrap()
}

第三方包

使用syn,quoteproc_macro可以实现自定义派生属性功能。

以下代码实现了派生属性New

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
extern crate proc_macro;
use {
syn::{Token, DeriveInput, parse_macro_input},
quote::*,
proc_macro2,
self::proc_macro::TokenStream,
};

#[proc_macro_derive(New)]
pub fn derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let result = match ast.data {
syn::Data::Struct(ref s) => new_for_struct(&ast, &s.fields),
_ => panic!("doesn't work with unions yet"),
};
result.into()
}

fn new_for_struct(ast: &syn::DeriveInput,fields: &syn::Fields) -> proc_macro2::TokenStream
{
match *fields {
syn::Fields::Named(ref fields) => {
new_impl(&ast, Some(&fields.named), true)
},
syn::Fields::Unit => {
new_impl(&ast, None, false)
},
syn::Fields::Unnamed(ref fields) => {
new_impl(&ast, Some(&fields.unnamed), false)
},
}
}

fn new_impl(ast: &syn::DeriveInput,
fields: Option<&syn::punctuated::Punctuated<syn::Field, Token![,]>>,
named: bool) -> proc_macro2::TokenStream
{
let struct_name = &ast.ident;

let unit = fields.is_none();
let empty = Default::default();

let fields: Vec<_> = fields.unwrap_or(&empty)
.iter()
.enumerate()
.map(|(i, f)| FieldExt::new(f, i, named)).collect();

let args = fields.iter().map(|f| f.as_arg());
let inits = fields.iter().map(|f| f.as_init());

let inits = if unit {
quote!()
} else if named {
quote![ { #(#inits),* } ]
} else {
quote![ ( #(#inits),* ) ]
};


let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let (new, doc) = (
syn::Ident::new("new", proc_macro2::Span::call_site()),
format!("Constructs a new `{}`.", struct_name)
);
quote! {
impl #impl_generics #struct_name #ty_generics #where_clause {
#[doc = #doc]
pub fn #new(#(#args),*) -> Self {
#struct_name #inits
}
}
}
}

struct FieldExt<'a> {
ty: &'a syn::Type,
ident: syn::Ident,
named: bool,
}
impl<'a> FieldExt<'a> {
pub fn new(field: &'a syn::Field, idx: usize, named: bool) -> FieldExt<'a> {
FieldExt {
ty: &field.ty,
ident: if named {
field.ident.clone().unwrap()
} else {
syn::Ident::new(&format!("f{}", idx), proc_macro2::Span::call_site())
},
named: named,
}
}
pub fn as_arg(&self) -> proc_macro2::TokenStream {
let f_name = &self.ident;
let ty = &self.ty;
quote!(#f_name: #ty)
}

pub fn as_init(&self) -> proc_macro2::TokenStream {
let f_name = &self.ident;
let init = quote!(#f_name);
if self.named {
quote!(#f_name: #init)
} else {
quote!(#init)
}
}
}

参考

使用Rust开发编译系统(C以及Rust编译的过程)

点击下方打赏按钮,获得支付宝二维码