Rust中的宣告巨集
前言
宣告式巨集(Declarative macros)使得你能夠寫出類似 match 表示式的東西,來操作你所提供的 Rust 程式碼。它使用你提供的程式碼來生成用於替換巨集呼叫的程式碼。
語法
巨集通過使用macro_rules!
來宣告,最為常見的一個宣告式巨集就是println!
。
macro_rules! 名稱 {
// 規則
(元變數:型別) => {轉錄器,用來擴充套件程式碼};
}
- 規則(rule):至少有一個,($matcher) => {$expansion}為一個規則。
- 匹配器(matcher):可以是()、{}或[],用來匹配輸入的引數。以上的
(元變數:型別)
- 元變數(metavariables):在匹配其中匹配元變數,可在轉錄器中使用。
- 轉錄器(transcriber): 用來在巨集匹配成功後, 進行程式碼替換。
在一個巨集中,可以有多個分支,巨集根據不同的引數展開到不同的程式碼。每個分支可以接收多個引數,這些引數使用$
符號開頭,然後跟著一個 token 型別:
-
item
:一個項(item),如一個函式,結構體,模組等。 -
block
:一個塊 (block),即一個語句塊或一個表示式,由花括號所包圍。 -
stmt
: 一個語句(statement)。 -
pat
:一個模式(pattern),如match表示式的(pattern)。 -
expr
: 一個表示式(expression)。 -
ty
:一個型別(type),如i32,u32,String,Option等。 -
ident
: 一個識別符號(indentfier)。 -
path
: 一個路徑(path),如foo
,::std::mem::replace
,transmute::<_, int>
,...。 -
meta
: 一個元資料項;例如#[...]
和#![...]
屬性,meta為[]內的值。 -
tt
:一個詞法樹(token tree)。 -
vis
:一個可能為空的Visibility
限定詞,如pub
。 -
lifetime
:一個生命週期(例如'foo
'static
, ...) -
literal
:一個字面量(e.g. "Hello World!", 3.14, ...)
使用宣告式巨集
建立
建立一個名叫add的過程巨集,有三個分支:
- 第一個分支沒有引數,在轉錄器中將程式碼替換為0。
- 第二個分支接收一個引數,並在轉錄器中將程式碼替換為它本身。
- 第三個分支接收兩個引數,並在轉錄器中將程式碼替換為兩個數相加。
macro_rules! add {
// 匹配器1
() => {{
0
}};
// 匹配器2
($a:expr) => {{
$a
}};
// 匹配器3
($a:expr,$b:expr) => {{
$a + $b
}};
}
fn main() {
println!("{:?}", add!()); // 0
println!("{:?}", add!(1)); // 1
println!("{:?}", add!(1, 2)); // 3
}
重複匹配
上面的例子每個匹配器只能匹配一種引數情況,如果我們要1+...+n,我們不能寫n個匹配器去匹配。這時候就要用到重複匹配。可以接收可變數量的引數,類似於正則表示式:
-
*
:用於匹配大於等於0個引數。 -
+
:用於匹配大於1引數。 -
?
:用於匹配0或1個引數。
我們可以簡化上面的程式碼為:
macro_rules! add {
// ($a:expr):要匹配的模式
// ,:分隔符,重複符號位?時不需要
// *:重複符號
($($a:expr),*)=>{{
// 沒引數時返回,必須有,否則報錯
0
// 重複程式碼塊
$(+$a)*
}};
}
fn main() {
println!("{:?}", add!()); // 0
println!("{:?}", add!(1)); // 1
println!("{:?}", add!(1, 2)); // 3
}
使用宣告式巨集進行高階解析
我們以Student結構體為例,建立一個StudentCopy的結構體並將Student中的屬性設定為pub。結構體如下:
#[derive(Debug,Clone)]
struct Student{
name:String,
age:i32,
gender:u8,
}
解析結構體
macro_rules! make_public{
(
// struct上定義的元資料
$(#[$meta:meta])*
// struct:可見範圍 struct 名稱
$vis:vis struct $struct_name:ident {
$(
// 欄位上定義的元資料
$(#[$field_meta:meta])*
// 欄位:可見範圍 名稱 :型別
$field_vis:vis $field_name:ident : $field_type:ty
),*$(,)+
}
) => {
$(#[$meta])*
pub struct $struct_name{
$(
$(#[$field_meta:meta])*
pub $field_name : $field_type,
)*
}
}
}
fn main() {
make_public! {
#[derive(Debug,Clone)]
struct Student{
name:String,
age:i32,
gender:u8,
}
};
let x = Student {
name: String::from("張三"),
age: 18,
gender: 1,
};
println!("{:?}", x);
}
檢視輸出
Student { name: "張三", age: 18, gender: 1 }
檢視巨集展開後代碼
cargo expand
檢視展開後的程式碼,可以看看到結構體Student相關屬性前都加了pub修飾符。
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
fn main() {
pub struct Student {
pub name: String,
pub age: i32,
pub gender: u8,
}
#[automatically_derived]
impl ::core::fmt::Debug for Student {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(
f,
"Student",
"name",
&&self.name,
"age",
&&self.age,
"gender",
&&self.gender,
)
}
}
#[automatically_derived]
impl ::core::clone::Clone for Student {
#[inline]
fn clone(&self) -> Student {
Student {
name: ::core::clone::Clone::clone(&self.name),
age: ::core::clone::Clone::clone(&self.age),
gender: ::core::clone::Clone::clone(&self.gender),
}
}
}
let x = Student {
name: String::from("張三"),
age: 18,
gender: 1,
};
{
::std::io::_print(
::core::fmt::Arguments::new_v1(
&["", "\n"],
&[::core::fmt::ArgumentV1::new_debug(&x)],
),
);
};
}
總結
本文列出宣告巨集的基本使用,希望對大家有幫助。
參考
Macros in Rust: A tutorial with examples: https://blog.logrocket.com/macros-in-rust-a-tutorial-with-examples/
A Beginner’s Guide to Rust Macros: https://medium.com/@phoomparin/a-beginners-guide-to-rust-macros-5c75594498f1
留個懸念
你覺得下面這段程式碼會輸出幾?
macro_rules! create_var {
() => {
let a = 1;
};
}
fn main() {
let a = 2;
create_var!();
println!("{}", a);
}