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.
原文鏈接:http://blog.hubwiz.com/2020/01/30/rust-macro/