Rust語言最強大的一個特點就是可以建立和利用宏/Macro。不過建立Rust宏看起來挺複雜,常常令剛接觸Rust的開發者心生畏懼。這片文章的目的就是幫助你了解Rust Macro的基本運作原理,學習如何建立自己的Rust宏。
相關連結: 線上學程式設計
1、什麼是Rust的宏/Macro?

如果你嘗試過Rust,應該已經用過Rust的宏了:
println!
。這個宏可以在終端輸出一行文本,并且支援變量的插值。
簡單地說,Rust宏讓你可以發明自己的文法,編寫出可以自行展開的代碼,也就是我們通常所說的元程式設計,你甚至可以用Rust宏來創作自己的DSL。
Rust宏的基本運作機制就是:首先比對宏規則中定義的模式,然後将比對結果綁定到變量,最後展開變量替換後的代碼。
不了解也沒有關系,讓我們繼續看。
2、如果建立Rust宏/Macro?
可以使用Rust預置的
macro_rules!
宏來建立一個新的Rust宏。
下圖展示了如何建立一個空白的Rust宏:
hey!
,這個宏什麼功能也沒有,我們現在隻關注它的結構:
() => {}
看起來很神秘,因為它不是标準的rust文法,是macro_rules!這個宏自己發明的,用來表示一條宏規則,
=>
左邊是比對模式,右邊是等待展開的代碼:
左邊的小括号部分是Rust宏的比對器/Matcher,用來比對模式并捕捉變量,這是我們發明自定義文法和DSL的關鍵所在。
右邊的大括号部分是Rust宏的轉碼器/Transcriber,也就是我們要應用比對器捕捉到的變量的部分,Rust編譯器将利用變量和這部分的代碼來生成實際的Rust代碼。
類似于Rust中的match語句,在
macro_rules!
中可以定義多條宏規則,例如:
macro_rules! hey{
() => {},
() => {}
}
3、模式比對與變量捕捉
現在我們看看Rust宏的模式是如何比對的。
在比對器/Matcher中,
$name
部分定義了變量名,比對結果将綁定到該變量以便應用到轉碼器/Transcriber中。在這個示例中,表示我們将Rust宏的比對結果存入變量
$name
。
冒号後面的部分被稱為選擇器/Designator,用于聲明我們要比對的類型。例如在這個示例中,我們使用的是表達式選擇器,也就是
expr
,這告訴Rust:比對一個表達式,然後存入
$name
變量。
表達式選擇器隻是Rust中衆多可用選擇器中的一個,下面是一些常見的Rust宏選擇器:
- item:條目,例如函數、結構、子產品等
- block:代碼塊
- stmt:語句
- pat:模式
- expr:表達式
- ty:類型
- ident:辨別符
- path:路徑,例如 foo、 ::std::mem::replace, transmute::<_, int>, ...
- meta:元資訊條目,例如 #[...]和 #![rust macro...] 屬性
- tt:詞條樹
那麼,現在如何在轉碼器/Transcriber中應用我們捕捉到的變量?
很簡單,在Rust宏轉碼器部分我們隻需要在正常的Rust代碼中,嵌入比對器捕捉到的變量就行了,沒什麼特别之處!
4、編寫第一個Rust宏
我們已經了解了如何編寫一個Rust宏,現在讓我們動手寫一個:
很簡單,對吧?
5、重複模式的提取與利用
我們用的許多Rust宏都可以支援非常多的輸入。以
vec!
宏為例,我們可以這樣調用它:
vec![rust macro1,2,3,4,5]
,或者這樣:
vec![rust macro1,2,3,,4,5,6,7,8]
那麼
vec!
宏是如何實作這一點的?很顯然它不會去定義成千上萬個變量來逐個儲存比對結果,秘密在于重複模式的比對:
我們隻需要把希望重複的模式寫在
$(...)
這部分,然後插入分隔符,在這裡也就是逗号,最後添加一個
*
符号,表示重複比對
$()
中的模式。
還有點暈?讓我們看個具體的例子:
在這個示例中,對于
hey!
宏,我們重複捕捉輸入表達式并存入變量
$name
,也就是說,所有捕捉到的表達式都綁定到變量
$name
了 —— 不妨把
$name
想象成數組變量。
6、用重複模式在Rust中實作Ruby的哈希表文法
如果你之前寫過Ruby程式,可能還記得在Ruby中定義哈希表的文法:key => value。現在我們可以用Rust宏來在Rust中實作哈希表的這種定義方法!
在Rust宏的比對器部分,我們使用模式
$key:expr => $value:expr
來分别捕捉
$key
和
$value
表達式,分隔符為
=>
。不過現在隻能比對一個鍵/值對,但是哈希表通常都是多個鍵值對的。應該如何實作?
答案是使用重複比對:
将我們要比對的鍵/值對模式放到
$(),*
,就可以進行重複比對了。COOL!!
那麼,如何應用捕捉到的鍵/值對?顯然,我們應該在Rust宏的轉碼器中建立哈希表對象,然後将捕捉到的所有鍵值對插入該哈希表:
在轉碼器中,注意代碼中的
$()*
,它的意思是其中的代碼會重複展開!
就像你看到的,當我們調用
map!("name" => "Finn", "gender" => "Boy")
時,我們在生成兩段重複的代碼。
key => value
将被轉碼為在Rust宏的轉碼器/transcriber中指定的代碼,也就是
hm.insert($key, $value)
,其中
$key
$value
是我們在Rust宏的比對器部分捕捉到的變量。
好了,讓我們看看完整的
map!
宏實作:
隻用了幾行代碼,我們就建立了一個功能完整的Rust宏!現在讓我們寫個小程式測試一下:
COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOL.
原文連結:
Rust宏程式設計新手指南 — 彙智網