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 還限制 expr 、 pat 、 pat_param 、 path 、 stmt 、 ty 、 vis 比對類型可以放在比對規則的位置,具體見類型的個別說明
比對類型如下:
block:表示用大括號{}包起來的可執行程式區塊- 可以比對這種區塊:
if ... { ... }、fn a() { ... } - 不能比對這種區塊:
struct A { ... }、impl A { ... }
- 可以比對這種區塊:
expr:表示運算或流程控制,不含任何變數操作- 運算、流程控制
if ... {}、函數呼叫a() let a = ...當中...的部份
let a = [ ... ];當中[ ... ]的部份
let a = { ... };當中{ ... }的部份,當中的...是另一段stmtlet a = if ... { 1 } else { 0 };當中的部份if ... { 1 } else { 0 }- 下個比對限定為字串
=>、,、;之一,或已達結尾
- 運算、流程控制
ident:表示識別名或關鍵字- 變數、函數、結構、trait 的名稱
extern 名稱 ...語句中的 ABI 名稱use ...;語句中經過的點的命名 (不含self、crate那些)- 各種關鍵字,比如
if、async、fn、struct等
item:表示宣告項目並包含該項目的可見性,具體包含- 模組語句
mod 模組名稱;mod 模組名稱 { ... } - 引用語句
use ...; extern crate ...;這個不熟,先寫著extern ...;這個不熟,先寫著- 巨集的使用與宣告
macro_rules! ...; - 在編譯期確定存在的項目,比如結構、函數、
impl ... {}、trait ... {} - 不能比對結構內的欄位、函數的參數
- 執行期擁有靜態生命週期的變數宣告 (
const及static),不含let
- 模組語句
lifetime:表示生命週期'_、'a、'static之類literal:表示一個布林、整數 (包含十六進制、八進制等)、浮點數、字元、字串值- 僅限直接表達,不包含任何函數呼叫、運算
meta:表示在#[...]與#![...]中的...可使用的語法- 可用語法包含
cfg(...)、cfg[...]、cfg{...} - 可用語法包含
a = "b",=後面的語法等同expr的比對範圍 - 不可指定泛型參數的
path比對範圍 (即不允許<>)
- 可用語法包含
pat、pat_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 {}這句是不符合的- 下個比對限定為字串
=>、,、=、|、if、in之一,或已達結尾
path:表示已宣告的模組、變數、結構、函數的完整或相對路徑,且可指定泛型參數- 純粹路徑,像是
std::vec、u8::MAX、std::vec::Vecstd::mem::size_of、drop(可在std::prelude找到) - 基於純粹路徑再指定其中泛型參數,像是
std::vec::Vec<u8>(其中 u8 比對範圍等同ty) - 對比
ty少了直接宣告型態的特性,比如無法比對[u8]、!(Never) 、_、*const u8(指標) - 對比
ty多了可比對已存在的函數、模組引用 - 下個比對限定為字串
=>、,、=、|、;、:、>、>>、[、{、as、where之一,或比對類型為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 + TraitB、impl Trait等 - 將型態視為特定 trait 的轉換,像是
<u8 as Default> - 還沒宣告的型態不能用它比對,所以
struct A {}的A無法比對!欄位型態定義則可以! - 下個比對限定為字串
=>、,、=、|、;、:、>、>>、[、{、as、where之一,或比對類型為block、已達結尾
- 直接寫明型態 (包含引用其他模組的型態),像是
vis:表示可見性,包含以下情況- 完全可見,像是
pub - 限定區域可見,像是
pub(crate)、pub(in super) - 預設的私有,比如沒指定可見性的情況
(tlborm/decl-macros參考的書說的,不過 reference 那本書看不出來,但這個說法合理) - 下個比對限定為字串
,,或比對類型為ident、ty、path,或已達結尾
- 完全可見,像是
重複比對
$( 比對規則 ) 分隔符 重複形式
// 以上空格是為了閱讀性,實際使用見下
$($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<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 比對不包含 <>
(
$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),* // 比對列表
$(,)? // 結尾可能為了方便編輯而帶有逗號,少了這個會比對失敗
)
Leave a Comment