天天看點

Rust宏程式設計快速教程

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

相關連結: 線上學程式設計

1、什麼是Rust的宏/Macro?

Rust宏程式設計快速教程

如果你嘗試過Rust,應該已經用過Rust的宏了:

println!

。這個宏可以在終端輸出一行文本,并且支援變量的插值。

簡單地說,Rust宏讓你可以發明自己的文法,編寫出可以自行展開的代碼,也就是我們通常所說的元程式設計,你甚至可以用Rust宏來創作自己的DSL。

Rust宏的基本運作機制就是:首先比對宏規則中定義的模式,然後将比對結果綁定到變量,最後展開變量替換後的代碼。

不了解也沒有關系,讓我們繼續看。

2、如果建立Rust宏/Macro?

可以使用Rust預置的

macro_rules!

宏來建立一個新的Rust宏。

下圖展示了如何建立一個空白的Rust宏:

hey!

,這個宏什麼功能也沒有,我們現在隻關注它的結構:

Rust宏程式設計快速教程

() => {}

看起來很神秘,因為它不是标準的rust文法,是macro_rules!這個宏自己發明的,用來表示一條宏規則,

=>

左邊是比對模式,右邊是等待展開的代碼:

Rust宏程式設計快速教程

左邊的小括号部分是Rust宏的比對器/Matcher,用來比對模式并捕捉變量,這是我們發明自定義文法和DSL的關鍵所在。

右邊的大括号部分是Rust宏的轉碼器/Transcriber,也就是我們要應用比對器捕捉到的變量的部分,Rust編譯器将利用變量和這部分的代碼來生成實際的Rust代碼。

類似于Rust中的match語句,在

macro_rules!

中可以定義多條宏規則,例如:

macro_rules! hey{
  () => {},
  () => {}
}           

3、模式比對與變量捕捉

現在我們看看Rust宏的模式是如何比對的。

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宏轉碼器部分我們隻需要在正常的Rust代碼中,嵌入比對器捕捉到的變量就行了,沒什麼特别之處!

4、編寫第一個Rust宏

我們已經了解了如何編寫一個Rust宏,現在讓我們動手寫一個:

Rust宏程式設計快速教程

很簡單,對吧?

5、重複模式的提取與利用

我們用的許多Rust宏都可以支援非常多的輸入。以

vec!

宏為例,我們可以這樣調用它:

vec![rust macro1,2,3,4,5]

,或者這樣:

vec![rust macro1,2,3,,4,5,6,7,8]

那麼

vec!

宏是如何實作這一點的?很顯然它不會去定義成千上萬個變量來逐個儲存比對結果,秘密在于重複模式的比對:

Rust宏程式設計快速教程

我們隻需要把希望重複的模式寫在

$(...)

這部分,然後插入分隔符,在這裡也就是逗号,最後添加一個

*

符号,表示重複比對

$()

中的模式。

還有點暈?讓我們看個具體的例子:

Rust宏程式設計快速教程

在這個示例中,對于

hey!

宏,我們重複捕捉輸入表達式并存入變量

$name

,也就是說,所有捕捉到的表達式都綁定到變量

$name

了 —— 不妨把

$name

想象成數組變量。

6、用重複模式在Rust中實作Ruby的哈希表文法

如果你之前寫過Ruby程式,可能還記得在Ruby中定義哈希表的文法:key => value。現在我們可以用Rust宏來在Rust中實作哈希表的這種定義方法!

Rust宏程式設計快速教程

在Rust宏的比對器部分,我們使用模式

$key:expr => $value:expr

來分别捕捉

$key

$value

表達式,分隔符為

=>

。不過現在隻能比對一個鍵/值對,但是哈希表通常都是多個鍵值對的。應該如何實作?

答案是使用重複比對:

Rust宏程式設計快速教程

将我們要比對的鍵/值對模式放到

$(),*

,就可以進行重複比對了。COOL!!

Rust宏程式設計快速教程

那麼,如何應用捕捉到的鍵/值對?顯然,我們應該在Rust宏的轉碼器中建立哈希表對象,然後将捕捉到的所有鍵值對插入該哈希表:

Rust宏程式設計快速教程

在轉碼器中,注意代碼中的

$()*

,它的意思是其中的代碼會重複展開!

Rust宏程式設計快速教程

就像你看到的,當我們調用

map!("name" => "Finn", "gender" => "Boy")

時,我們在生成兩段重複的代碼。

key => value

将被轉碼為在Rust宏的轉碼器/transcriber中指定的代碼,也就是

hm.insert($key, $value)

,其中

$key

$value

是我們在Rust宏的比對器部分捕捉到的變量。

好了,讓我們看看完整的

map!

宏實作:

Rust宏程式設計快速教程

隻用了幾行代碼,我們就建立了一個功能完整的Rust宏!現在讓我們寫個小程式測試一下:

Rust宏程式設計快速教程

COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOL.

原文連結:

Rust宏程式設計新手指南 — 彙智網