Rust 巨集筆記

2021-10-25 01:24:34 字數 3419 閱讀 4919

rust 巨集筆記

這篇文章說的是?

rust 的巨集。

宣告巨集(declarative macro)和過程巨集(procedural macro)。前者指的是用某種語法直接宣告出的巨集。後者是對應直接生成抽象語法樹的過程的巨集。

直覺上過程巨集更隱式,更全能;宣告巨集更可讀,更直接。

如何定義宣告巨集?

現在用 macro_rules!。以後可能還有別的辦法。

如何定義過程巨集?

以後再說。

巨集按照使用方式分類:

屬性巨集:給宣告新增屬性的巨集,例如 #[derive(debug)] 和 #[test]。

呼叫巨集:像函式一樣的巨集,例如 println!。

**分類和使用方式分類之間的關係如何?

目前的宣告巨集都是用 macro_rules! 宣告出的,它宣告出的一定是呼叫巨集。過程巨集可以產生屬性巨集,也可以產生呼叫巨集。

也就是說,屬性巨集都是過程巨集,呼叫巨集可能是宣告巨集或者過程巨集。

println! 巨集大概是什麼樣子?

macro_rules! println
這個巨集有幾部分?

有三個部分,輸入分別是 ()、(fmt

:exp

r)和(

fmt:expr) 和 (

fmt:ex

pr)和

(fmt: expr, (

((args:tt)*),依次擴充套件成 => 後,圓括號內部的部分。每個部分是一條規則,每條規則以 ; 結尾。

=> 後的圓括號是必須的嗎?

不能省略,但是可以換成 {} 或者 。

$fmt: expr 是什麼?

$fmt 是對巨集引數的捕獲,類似於函式的引數。expr 表示這個捕獲的型別是表示式,也就是會它會生成具體的值。具體到此處,它代表生成 println! 的格式字串的表示式。

捕獲有什麼用?

巨集的替換結果裡可以用 fmt

代表要替

換這個捕

獲。例如

prin

tln!

("he

llo"

)中

,fmt 代表要替換這個捕獲。例如 println!("hello") 中,

fmt代表要

替換這個

捕獲。例

如pri

ntln

!("h

ello

")中,

fmt: expr 捕獲了 「hello」,所以 print!(concat!($fmt, "\n)) 中的 $fmt 會被替換為 「hello」,所以展開成 print!(concat!(「hello」, 「\n」))。

展開的巨集中如果還有巨集,還會繼續展開嗎?

會,上面 println! 展開之後的內容中有 print! 和 concat!,它們都會再次展開。這是理所當然的行為,這個問題只是為了讓 rust 的巨集跟 c++ 的巨集劃清界限。

都有什麼捕獲型別?

型別 	意義

item 語言項,模組、定義、宣告等

block **塊,花括號限定的**

stmt 語句,分號結尾的**

expr 表示式,會生成具體的值

pat 模式

ty 型別

ident 識別符號

path 路徑,指從 crate 到 mod 的定位

meta 元資訊

tt tokentree 的縮寫,詞條樹

vis 可見性,例如 pub

lifetime 生命週期引數

(

((arg:tt)* 是什麼意思?

單獨看 $arg:tt 表示匹配乙個詞條樹的捕獲,在外面套上 $()* 表示匹配若干次詞條樹。

(

((arg)* 是什麼意思?

單看 $arg,表示在巨集裡替換捕獲 $arg,外面套上 $()* 表示使用所有匹配的捕獲。這個用法跟它的捕獲語法是對應的。

(

((arg:tt)* 能匹配什麼?

println! 在第乙個引數之後的所有東西。這個巨集不止可以傳遞像函式一樣的引數,還可以像 python 那樣傳遞命名引數,例如:

println!(「hello, 」, name=「luna」);

這樣的引數 (

((arg:tt)* 也能捕獲到。

如果想只捕獲(不定個數個)函式引數應該如何做?

用 (

((arg: expr),* 或者 (

((arg: expr,)*。

這兩個有什麼不同?

後者也匹配逗號結尾的引數列表。

rust 的函式引數列表最後可以新增逗號,也可以不加。如果想讓巨集表現的盡量接近函式,應該兩種情況都處理。

舉個例子。

macro_rules! hash_map };

($($key:expr => $value:expr),*) =>

(hash_map! ($($key => $value),*));

}

怎麼使用?

let map = hash_map! (1 => "one", 2 => "two", 3 => "three");
如何獲取巨集可變引數的長度?

沒有直接的辦法,但可以想些技巧。

macro_rules! unit 

macro_rules! count

它是如何工作的?

unit! 接受任意什麼東西,返回乙個 unit(())。count! 巨集把引數填給 unit!,構造了乙個 unit 陣列,陣列長度就是引數的個數。

好處是,unit 不佔空間,unit 的陣列也是。

count! 巨集需要 unit! 巨集才能工作,但 unit! 巨集本身沒什麼用,能不能把 unit! 變成私有的?

也沒有直接的辦法。基本上的技巧是換成乙個比較難直接用到的規則:

macro_rules! count
要是有人非要寫 count!(@unit …),也沒法阻止。但那個奇奇怪怪的 @ 已經暗示了這是乙個內部實現,這就足夠了。

#[macro_export] 有什麼作用?

標記乙個巨集可以在其他包中使用。也就是說,預設情況下,巨集不能在定義的包外使用。

#[macro_use] 有什麼用?

在 rust 2015 中,在外部包的宣告語前,用它標記要匯入另乙個包的巨集。另外,它還標記乙個 mod 的巨集可以在外面使用。

巨集重名會怎樣?

後匯入的會覆蓋先前匯入的,不會發生錯誤。

$crate 有什麼用?

它看起來像乙個捕獲,但不是在巨集引數列表裡捕獲的。它會擴充套件成當前包的名字。需要這個捕獲的原因是,當前包無法決定此包被其他包匯入的時候,使用的是什麼名字。所以如果要使用當前包的函式或者別的東西,就需要從 $crate 開始寫路徑。

Rust 過程巨集

一 新增過程巨集依賴庫1 過程巨集,類似其他語言的執行時反射機制 2 官方的過程巨集庫為proc macro,不推薦直接使用 3 推薦更友好的syn quote和proc macro2這3個庫 4 過程巨集,必須寫在單獨的lib型別的crate中 lib proc macro true depend...

rust巨集的復用

由於這一部分別人已經寫過就不多贅述,放個鏈結 假如我們在crate中想要寫乙個巨集,為了美觀將其單獨放在了乙個檔案中,假設叫macros.rs,如下圖所示 如果我們想在同一層級的node.rs中使用這個巨集,那麼需要做的就是在這個巨集前加上 macro export 並且在lib.rs中新增 mac...

Rust中巨集的理解

巨集相比函式是相對難以理解的,更加難以掌握,編寫理解以及除錯都很有困難。但它的存在肯定是有它比較獨特的地方的。相比函式,巨集是用來生成 的,在呼叫巨集的地方,編譯器會先將巨集進行展開,生成 然後再編譯展開後的 在rust中,函式是不接受任意多個引數的,巨集可以辦到。巨集定義格式macro rules...