Rust 巨集語法 macro_rules

參考:

  • https://doc.rust-lang.org/reference/macros-by-example.html
  • https://veykril.github.io/tlborm/decl-macros/minutiae/fragment-specifiers.html

巨集語法太廣了,看起來還有 @內部規則 之類的用法,之後再看…

定義巨集

macro_rules! 巨集名稱 {
	比對規則 => { 產生程式 };
	...
}

比對規則

參數輸入

使用巨集時填入的參數會基於 Rust 語法被編譯器分解為帶階層的字串組,再對規則做比對

  • a+b 會分解為 a + b
  • ()[]{} 會產生階層,檢查是否成對、限制單次變數比對範圍
  • 除非成對的階層開頭包含在單次比對範圍內,否則遇到 )]} 時視為已比對到結尾
  • 具體拆成多細可用 tt 比對類型做測試

比對類型

$變數名稱:比對類型

比對類型決定可以比對哪些字串組,以及符合的字串組可以放在輸出區塊的哪些地方,比如:

  • expr 比對類型可以連續比對 a+b …,直到遇到在語法上無法符合為止
  • ty 比對類型作為型態,輸出時無法填在變數名稱欄位 (即使輸出可以成功編譯也不行)

除此之外,為了未來的相容性,Rust 還限制 exprpatpat_parampathstmttyvis 比對類型可以放在比對規則的位置,具體見類型的個別說明

比對類型如下:

  • block :表示用大括號 {} 包起來的可執行程式區塊
    • 可以比對這種區塊:if ... { ... }fn a() { ... }
    • 不能比對這種區塊:struct A { ... }impl A { ... }
  • expr :表示運算或流程控制,不含任何變數操作
    • 運算、流程控制 if ... {}、函數呼叫 a()
    • let a = ... 當中 ... 的部份
    • let a = [ ... ]; 當中 [ ... ] 的部份
    • let a = { ... }; 當中 { ... } 的部份,當中的 ... 是另一段 stmt
    • let a = if ... { 1 } else { 0 }; 當中 if ... { 1 } else { 0 } 的部份
    • 下個比對限定為字串 =>,; 之一,或已達結尾
  • ident :表示識別名或關鍵字
    • 變數、函數、結構、trait 的名稱
    • extern 名稱 ... 語句中的 ABI 名稱
    • use ...; 語句中經過的點的命名 (不含 selfcrate 那些)
    • 各種關鍵字,比如 ifasyncfnstruct
  • item :表示宣告項目並包含該項目的可見性,具體包含
    • 模組語句 mod 模組名稱; mod 模組名稱 { ... }
    • 引用語句 use ...;
    • extern crate ...; 這個不熟,先寫著
    • extern ...; 這個不熟,先寫著
    • 巨集的使用與宣告 macro_rules! ...;
    • 在編譯期確定存在的項目,比如結構、函數、 impl ... {}trait ... {}
    • 不能比對結構內的欄位、函數的參數
    • 執行期擁有靜態生命週期的變數宣告 ( conststatic ),不含 let
  • lifetime :表示生命週期 '_'a'static 之類
  • literal :表示一個布林、整數 (包含十六進制、八進制等)、浮點數、字元、字串值
    • 僅限直接表達,不包含任何函數呼叫、運算
  • meta :表示在 #[...]#![...] 中的 ... 可使用的語法
    • 可用語法包含 cfg(...)cfg[...]cfg{...}
    • 可用語法包含 a = "b"= 後面的語法等同 expr 的比對範圍
    • 不可指定泛型參數的 path 比對範圍 (即不允許 <>)
  • patpat_param :表示一種對結構、結構內欄位的比對及取值及借值方式,自身沒有回傳特性
    • if let $p:pat = v {}$p:pat 就是符合此規則的點
    • while let $p:pat = v {}$p:pat 就是符合此規則的點
    • match v { $p:pat => {}, }$p:pat 就是符合此規則的點
    • _ ,出現於 if let Some(_) = v {} 或函數的參數名稱
    • if v == 1 {} 這句是不符合的
    • 下個比對限定為字串 =>,=|ifin 之一,或已達結尾
  • path :表示已宣告的模組、變數、結構、函數的完整或相對路徑,且可指定泛型參數
    • 純粹路徑,像是 std::vecu8::MAXstd::vec::Vec std::mem::size_ofdrop (可在 std::prelude 找到)
    • 基於純粹路徑再指定其中泛型參數,像是 std::vec::Vec<u8> (其中 u8 比對範圍等同 ty )
    • 對比 ty 少了直接宣告型態的特性,比如無法比對 [u8]! (Never) 、 _*const u8 (指標)
    • 對比 ty 多了可比對已存在的函數、模組引用
    • 下個比對限定為字串 =>,=|;:>>>[{aswhere 之一,或比對類型為 block 、已達結尾
  • stmt :表示一段 Rust 語法
    • 包含了 item 所比對的語句
    • 包含了 expr 所比對的語句
    • 包含變數宣告 let mut v: u8 = 1; ,這個比對也可以拆成 let $p:pat: $t: ty = $e:expr;
    • 下個比對限定為字串 =>,; 之一,或已達結尾
  • tt :表示 TokenTree,其實就是 AST 語法樹,將所有語法拆開,並且以 ()[]{} 做階層化
    • 每個比對項目都可以被註解分隔 let/**/a/**/=/**/1/**/+/**/1/**/;
    • 階層化會整塊比對回來 fn_call/**/()/**/;
  • ty :表示型態,可以視為指定型態或引用已宣告的型態,包含以下情況
    • 直接寫明型態 (包含引用其他模組的型態),像是 u8[u8](u8, u8)Fn() -> u8! (Never) 、 _*const u8 (指標) 、 std::vec::Vec<u8>
    • 泛型型態參數,像是 dyn TraitA + TraitBimpl Trait
    • 將型態視為特定 trait 的轉換,像是 <u8 as Default>
    • 還沒宣告的型態不能用它比對,所以 struct A {}A 無法比對!欄位型態定義則可以!
    • 下個比對限定為字串 =>,=|;:>>>[{aswhere 之一,或比對類型為 block 、已達結尾
  • vis :表示可見性,包含以下情況
    • 完全可見,像是 pub
    • 限定區域可見,像是 pub(crate)pub(in super)
    • 預設的私有,比如沒指定可見性的情況
      (tlborm/decl-macros 參考的書說的,不過 reference 那本書看不出來,但這個說法合理)
    • 下個比對限定為字串 , ,或比對類型為 identtypath ,或已達結尾

重複比對

$( 比對規則 ) 分隔符 重複形式
// 以上空格是為了閱讀性,實際使用見下
$($t:ty),*

分隔符

  • 分隔符通常使用 ,;
  • 不可使用 ()[]{} 這些字元,以及可用於重複形式的字元
  • 可以使用關鍵字、名稱、運算子、常見分隔符 ,;,但只能使用一個
    • 例:不能把 a= 當成分隔符,因為 a 是名稱,但 = 是運算子
    • 例:可以把 ab 當成分隔符,因為名稱可由多個字元組成,可視為”一個”

重複形式

  • * :表示比對 0 次或以上
  • + :表示比對 1 次或以上
  • ? :表示比對 0 次或 1 次

重複比對限制

重複比對方式與輸出方式必須成對,所以

  • ($($i:ident)*) => ($($i),*) 是可以接受的,分隔符沒有成對限制
  • ($($i:ident)*) => ($i $($i)*) 是不能接受的, $i 在比對時放在 $(...)* 裡面,所以輸出也只能放 $(...)* 裡面
  • ($($i:ident)+) => ($($i)*) 是不能接受的,因為次數不一致

組合重複比對

( $($t1:ty),* ; $($t2:ty),*) => ( ( $( ($t1, $t2) );* ) )

在這個範例中,由於 $t1$t2 成對輸出,所以兩者的比對次數需相同,所以

  • (A, B, C; D, E, F) 會輸出 ((A, D); (B, E); (C, F))
  • (A, B, C; D, E) 會因 $t2 的比對次數比 $t1 少而報錯

語法分解示範

// 原始
struct A {
	pub t: Vec&lt;u8>,
}
// A 為結構名稱,在宣告時它只是個名稱!所以不能用 ty 比對它!

// 比對1-1
(
	$struct_vis:vis // 可見性
	$struct_key:ident // 關鍵字 struct
	$struct_name:ident // 結構名稱
	{
		$(
			$field_vis:vis // 欄位可見性
			$field_name:ident // 欄位名稱
			:
			$field_type:ty // 欄位型態
		),* // 0 ~ 不設限個欄位
		$(,)?
	}
) => {
	$struct_vis $struct_key $struct_name {
		$($field_vis $field_name: $field_type),*
	}
}

// 比對1-2:ident 比對 vis,但 不完全包含 ty,因為 ident 比對不包含 &lt;>
(
	$struct_vis:vis $struct_key:ident $struct_name:ident {
		$($field_vis:ident $field_name:ident:$field_type:ty),*
		$(,)?
	}
) => {
	$struct_vis $struct_key $struct_name {
		$($field_vis $field_name: $field_type),*
	}
}

// 比對2:tt 基本可以比對所有東西...
(
	$struct_vis:vis $struct_key:ident $struct_name:ident {
		$($tt:tt)*
	}
) => {
	$struct_vis $struct_key $struct_name {
		$($tt)*
	}
}

// 比對4:把細節拿掉
( $struct:item ) => { $struct }

比對技巧

比對列表

(
	$($i:ident),* // 比對列表
	$(,)? // 結尾可能為了方便編輯而帶有逗號,少了這個會比對失敗
)

Related posts

Serde 對現有值進行複寫的反序列化

Serde 反序列化除了基於預設值複寫外,也可以基於其他現有值複寫,通過不在文件上寫明的公開穩定特徵方法 serde::Deserialize::deserialize_in_place() 可以做到。

原始碼中的註解表示,這個方法被隱藏的原因是新手很少使用。我說這很常用吧....

在 Rust 用 trait 實現 OOP 的單層多重繼承

有了這寫法,就可以輕鬆實現多種功能隨意組合了

純方法,沒有依賴結構

直接寫 trait 跟 impl Trait for Struct {}

有依賴結構

// 功能 A
mod func_a {
#
pub struct FuncAStruct...

vim 操作

啟動參數

-p 可將多個檔案各別開到標籤頁

快速鍵

= 文字重新排版、格式化

za 開關 fold

. 重複文字操作

@: 重複指令操作

^ / Home 前往行開頭

$ / End 前往行結尾

g t...

原子操作 memory order 設定

這個設定可以限制 CPU 亂序執行的執行順序,簡單說明如表:

設定值讀寫順序relaxedOO不影響acquireOX後面的程式要等這行執行完了才會執行,即後面的程式不會早於此操作執行。releaseXO前面的程式要都執行完了才會執行後面的程式,即前面的程式不會晚於此操作執行acq_relOO等同 acquire + releaseseq_cstOO視操作類型加上 acquire, release, acq_rel 其一,並依序向其他線程同步consumeOX等同 acquire,但只向與該變數有關的線程同步據說編譯器沒實作這東西,沒驗證是否為真

整理資訊原文

memory_order_relaxed:純原子操作,不影響順序memory_order_acquire:只能用於取值,且確保後面變數的讀寫操作不會再取值前開始(即保證後面的變數操作不會被往前排)memory_order_release:只能用於寫值,寫值前會確保前面變數的讀寫操作都完成了(即保證前面的變數操作不會被往後排)關聯點:使用 memory_order_acquire 的線程會向使用 memory_order_release...

Leave a Comment

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *