一焊唬、 rust編譯過程
從上面的編譯過程圖伴澄,可以看到
聲明宏
和 過程宏
被編譯到AST中過程是不同的:
- 聲明宏:通過macro_rule 定義的宏最終只是被解析為TokenStream;
- 過程宏: 定義的過程宏首先被解析為proc_macro::TokenStream珊皿,接著再被解析為proc_macro2::TokenStream,再使用Syn庫解析為AST(這里的不同于編譯生成的AST)寸齐,最后再通過Quote解析為TokenStream,最終會(huì)被擴(kuò)展到編譯過程TokenStream中,進(jìn)而再被編譯AST;
二脊另、過程宏
1济赎、樣例
此處定義一個(gè)屬性過程宏的樣例
2鉴逞、定義過程宏
- 準(zhǔn)備工作
主要是Cargo.toml文件中
[lib]
proc-macro=true # 開啟過程宏
# 引入如下三個(gè)crate的原因 參考上面編譯過程圖:在生成最終的TokenStream前,需要依賴這三個(gè)crate
[dependencies]
proc-macro2 = "1.0.34"
syn = {version="1.0.82", feature="full"}
quote ="1.0.10"
項(xiàng)目結(jié)構(gòu)
過程宏需要定義在一個(gè)單獨(dú)的crate中司训,主要是因?yàn)檫^程宏是一段在編譯crate前构捡,對(duì)其代碼
進(jìn)行加工的代碼,而這段是需要在編譯后執(zhí)行的豁遭。若是將定義過程宏和使用過程宏放到同一個(gè)crate中叭喜,就會(huì)陷入編譯“死鎖”:
1、要編譯的代碼需要運(yùn)行過程宏進(jìn)行展開蓖谢,否則代碼是不完整的,無法編譯crate譬涡;
2闪幽、不能編譯crate,那么在crate中的過程宏就無法執(zhí)行涡匀,就不能展開被過程宏“修飾”的代碼定義過程宏
use proc_macro::TokenStream;
/// 定義一個(gè)屬性過程宏
/// 盯腌??陨瘩?輸入?yún)?shù):TokenStream 輸出參數(shù): TokenStream【解釋見`項(xiàng)目結(jié)構(gòu)`】
/// 不做任何加工腕够,直接輸出item
#[proc_macro_attribute]
pub fn my_first_attr_proc_macro(attr: TokenStream, item: TokenStream) -> TokenStream {
eprintln!("===輸出attr");
eprintln!(" {:#?}", attr);
eprintln!("===輸出item");
eprintln!("{:#?}", item);
item
}
- 使用過程宏
1、Cargo.toml文件引入過程宏crate
[dependencies]
produceral_macro={path= "../procedural_macro_demos/procedural_macro" } # 本地路徑引用
2舌劳、使用過程宏
通過#[crate_name::proc_macro_func("過程宏名字-任意")]
類似 #[produceral_macro::my_first_attr_proc_macro("my_first_procedural_macro")]
#[produceral_macro::my_first_attr_proc_macro("my_first_procedural_macro")]
fn add(a: u64, b: u64) -> () {
eprintln!("a={:?}, b={:?}, a+b={:?}", a, b , (a+b));
}
rust過程宏本質(zhì)就是一個(gè)編譯環(huán)節(jié)的“過濾器”或者說是一個(gè)“中間件”帚湘,接收一段用戶編寫的源代碼,再做一通“轉(zhuǎn)換”操作甚淡,然后返回給編譯器一段經(jīng)過修改的代碼大诸。
目前過程宏代碼的調(diào)試,最好通過print來進(jìn)行
最終生成的TokenStream是沒有任何語義信息
的,是通過樹形結(jié)構(gòu)的數(shù)據(jù)組織资柔,表達(dá)了用戶源代碼各個(gè)語言元素的類型及其相互之間的關(guān)系焙贷;每個(gè)語言元素都有一個(gè)span屬性,記錄該元素在用戶源代碼中的位置贿堰。同時(shí)不同類型的節(jié)點(diǎn)有各自獨(dú)有的屬性辙芍。
而Rust過程宏就是自己能夠手動(dòng)修改輸入變量中的值,比如樣例中的attr
,item
,換句話說等價(jià)于加工原始輸入代碼羹与,最后將加工后的代碼返回給編譯器即可.
三故硅、proc-macro2\syn\quote三個(gè)包在過程宏中的應(yīng)用
使用syn、quote等crate模擬過程宏“修改”源代碼注簿,并將結(jié)果以TokenStream給到編譯器:
#[proc_macro_attribute]
pub fn my_first_attr_proc_macro_parse(attr: TokenStream, input: TokenStream) -> TokenStream {
eprintln!("===attr: {:#?}", attr);
eprintln!("===item: {:#?}", input);
// 通過syn來解析輸入的TokenStream
let input_fn = syn::parse_macro_input!(input as syn::ItemFn);
// 獲取該token對(duì)應(yīng)的類型:ident
let name = input_fn.sig.ident.clone();
// 輸出TokenStream給到編譯器
TokenStream::from(quote! { // 使用quote庫來構(gòu)建編譯器需要的TokenStream
fn #name () {
#input_fn
for i in 0..3 {
println!("loop time {}", i);
let r = std::panic::catch_unwind(|| {
#name();
});
if r.is_ok() {
return;
}
if i == 2 {
std::panic::resume_unwind(r.unwrap_err());
}
}
}
})
}
測試代碼
#[procedural_macro::my_first_attr_proc_macro_parse]
fn test_proc_macro() {
assert_eq!(1,1); // 運(yùn)行正常
assert_eq!(1,22); // 觸發(fā)panic
}
四契吉、編譯過程宏代碼
#輸出未進(jìn)行宏擴(kuò)展的ast樹
$ cargo rustc -- -Z ast-json-noexpand=yes
#輸出宏擴(kuò)展后的ast樹
$ cargo rustc -- -Z ast-json=yes
#輸出hir格式的中間描述
$ cargo rustc -- -Z unpretty=hir
#輸出hir格式并帶有類型信息的中間描述
$ cargo rustc -- -Z unpretty=hir,typed
#輸出hir格式并帶有完整樹結(jié)構(gòu)的中間描述
$ cargo rustc -- -Z unpretty=hir-tree
#輸出mir格式的中間描述
$ cargo rustc -- -Z unpretty=mir
#### 代碼編譯中間代碼 ####
#輸出llvm ir格式的中間描述
$ rustc --emit llvm-ir lrfrc.rs
#輸出匯編格式的中間描述
$ rustc --emit asm lrfrc.rs
#分析中間匯編輸出
$ rustc -C opt-level=3 --emit=obj lrfrc.rs
$ size -A lrfrc.o
$ objdump -d lrfrc.o