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...