ch19-06-macros.md
commit acc806a06b5a23c7397b7218aecec0e774619512
我們已經(jīng)在本書中使用過像 println!
這樣的宏了,不過還沒完全探索什么是宏以及它是如何工作的。宏(Macro)指的是 Rust 中一系列的功能:使用 macro_rules!
的 聲明(Declarative)宏,和三種 過程(Procedural)宏:
#[derive]
? 宏在結(jié)構(gòu)體和枚舉上指定通過 ?derive
? 屬性添加的代碼我們會(huì)依次討論每一種宏,不過首要的是,為什么已經(jīng)有了函數(shù)還需要宏呢?
從根本上來說,宏是一種為寫其他代碼而寫代碼的方式,即所謂的 元編程(metaprogramming)。在附錄 C 中會(huì)探討 derive
屬性,其生成各種 trait 的實(shí)現(xiàn)。我們也在本書中使用過 println!
宏和 vec!
宏。所有的這些宏以 展開 的方式來生成比你所手寫出的更多的代碼。
元編程對(duì)于減少大量編寫和維護(hù)的代碼是非常有用的,它也扮演了函數(shù)扮演的角色。但宏有一些函數(shù)所沒有的附加能力。
一個(gè)函數(shù)簽名必須聲明函數(shù)參數(shù)個(gè)數(shù)和類型。相比之下,宏能夠接收不同數(shù)量的參數(shù):用一個(gè)參數(shù)調(diào)用 println!("hello")
或用兩個(gè)參數(shù)調(diào)用 println!("hello {}", name)
。而且,宏可以在編譯器翻譯代碼前展開,例如,宏可以在一個(gè)給定類型上實(shí)現(xiàn) trait 。而函數(shù)則不行,因?yàn)楹瘮?shù)是在運(yùn)行時(shí)被調(diào)用,同時(shí) trait 需要在編譯時(shí)實(shí)現(xiàn)。
實(shí)現(xiàn)宏不如實(shí)現(xiàn)函數(shù)的一面是宏定義要比函數(shù)定義更復(fù)雜,因?yàn)槟阏诰帉懮?Rust 代碼的 Rust 代碼。由于這樣的間接性,宏定義通常要比函數(shù)定義更難閱讀、理解以及維護(hù)。
宏和函數(shù)的最后一個(gè)重要的區(qū)別是:在一個(gè)文件里調(diào)用宏 之前 必須定義它,或?qū)⑵湟胱饔糜?,而函?shù)則可以在任何地方定義和調(diào)用。
Rust 最常用的宏形式是 聲明宏(declarative macros)。它們有時(shí)也被稱為 “macros by example”、“macro_rules!
宏” 或者就是 “macros”。其核心概念是,聲明宏允許我們編寫一些類似 Rust match
表達(dá)式的代碼。正如在第六章討論的那樣,match
表達(dá)式是控制結(jié)構(gòu),其接收一個(gè)表達(dá)式,與表達(dá)式的結(jié)果進(jìn)行模式匹配,然后根據(jù)模式匹配執(zhí)行相關(guān)代碼。宏也將一個(gè)值和包含相關(guān)代碼的模式進(jìn)行比較;此種情況下,該值是傳遞給宏的 Rust 源代碼字面值,模式用于和前面提到的源代碼字面值進(jìn)行比較,每個(gè)模式的相關(guān)代碼會(huì)替換傳遞給宏的代碼。所有這一切都發(fā)生于編譯時(shí)。
可以使用 macro_rules!
來定義宏。讓我們通過查看 vec!
宏定義來探索如何使用 macro_rules!
結(jié)構(gòu)。第八章講述了如何使用 vec!
宏來生成一個(gè)給定值的 vector。例如,下面的宏用三個(gè)整數(shù)創(chuàng)建一個(gè) vector:
let v: Vec<u32> = vec![1, 2, 3];
也可以使用 vec!
宏來構(gòu)造兩個(gè)整數(shù)的 vector 或五個(gè)字符串 slice 的 vector 。但卻無法使用函數(shù)做相同的事情,因?yàn)槲覀儫o法預(yù)先知道參數(shù)值的數(shù)量和類型。
在示例 19-28 中展示了一個(gè) vec!
稍微簡(jiǎn)化的定義。
文件名: src/lib.rs
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
示例 19-28: 一個(gè) vec!
宏定義的簡(jiǎn)化版本
注意:標(biāo)準(zhǔn)庫中實(shí)際定義的 ?
vec!
? 包括預(yù)分配適當(dāng)量的內(nèi)存的代碼。這部分為代碼優(yōu)化,為了讓示例簡(jiǎn)化,此處并沒有包含在內(nèi)。
#[macro_export]
注解表明只要導(dǎo)入了定義這個(gè)宏的crate,該宏就應(yīng)該是可用的。 如果沒有該注解,這個(gè)宏不能被引入作用域。
接著使用 macro_rules!
和宏名稱開始宏定義,且所定義的宏并 不帶 感嘆號(hào)。名字后跟大括號(hào)表示宏定義體,在該例中宏名稱是 vec
。
vec!
宏的結(jié)構(gòu)和 match
表達(dá)式的結(jié)構(gòu)類似。此處有一個(gè)分支模式 ( $( $x:expr ),* )
,后跟 =>
以及和模式相關(guān)的代碼塊。如果模式匹配,該相關(guān)代碼塊將被執(zhí)行。這里這個(gè)宏只有一個(gè)模式,那就只有一個(gè)有效匹配方向,其他任何模式方向(譯者注:不匹配這個(gè)模式)都會(huì)導(dǎo)致錯(cuò)誤。更復(fù)雜的宏會(huì)有多個(gè)分支模式。
宏定義中有效模式語法和在第十八章提及的模式語法是不同的,因?yàn)楹昴J剿ヅ涞氖?Rust 代碼結(jié)構(gòu)而不是值?;剡^頭來檢查下示例 19-28 中模式片段什么意思。對(duì)于全部的宏模式語法,請(qǐng)查閱參考。
首先,一對(duì)括號(hào)包含了整個(gè)模式。接下來是美元符號(hào)( $
),后跟一對(duì)括號(hào),其捕獲了符合括號(hào)內(nèi)模式的值用以在替代代碼中使用。$()
內(nèi)則是 $x:expr
,其匹配 Rust 的任意表達(dá)式,并將該表達(dá)式命名為 $x
。
$()
之后的逗號(hào)說明一個(gè)可有可無的逗號(hào)分隔符可以出現(xiàn)在 $()
所匹配的代碼之后。緊隨逗號(hào)之后的 *
說明該模式匹配零個(gè)或更多個(gè) *
之前的任何模式。
當(dāng)以 vec![1, 2, 3];
調(diào)用宏時(shí),$x
模式與三個(gè)表達(dá)式 1
、2
和 3
進(jìn)行了三次匹配。
現(xiàn)在讓我們來看看與此分支模式相關(guān)聯(lián)的代碼塊中的模式:匹配到模式中的$()
的每一部分,都會(huì)在(=>
右側(cè))$()*
里生成temp_vec.push($x)
,生成零次還是多次取決于模式匹配到多少次。$x
由每個(gè)與之相匹配的表達(dá)式所替換。當(dāng)以 vec![1, 2, 3];
調(diào)用該宏時(shí),替換該宏調(diào)用所生成的代碼會(huì)是下面這樣:
{
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
}
我們已經(jīng)定義了一個(gè)宏,其可以接收任意數(shù)量和類型的參數(shù),同時(shí)可以生成能夠創(chuàng)建包含指定元素的 vector 的代碼。
macro_rules!
中有一些奇怪的地方。在將來,會(huì)有第二種采用 macro
關(guān)鍵字的聲明宏,其工作方式類似但修復(fù)了這些極端情況。在此之后,macro_rules!
實(shí)際上就過時(shí)(deprecated)了。在此基礎(chǔ)之上,同時(shí)鑒于大多數(shù) Rust 程序員 使用 宏而非 編寫 宏的事實(shí),此處不再深入探討 macro_rules!
。請(qǐng)查閱在線文檔或其他資源,如 “The Little Book of Rust Macros” 來更多地了解如何寫宏。
第二種形式的宏被稱為 過程宏(procedural macros),因?yàn)樗鼈兏窈瘮?shù)(一種過程類型)。過程宏接收 Rust 代碼作為輸入,在這些代碼上進(jìn)行操作,然后產(chǎn)生另一些代碼作為輸出,而非像聲明式宏那樣匹配對(duì)應(yīng)模式然后以另一部分代碼替換當(dāng)前代碼。
有三種類型的過程宏(自定義派生(derive),類屬性和類函數(shù)),不過它們的工作方式都類似。
創(chuàng)建過程宏時(shí),其定義必須駐留在它們自己的具有特殊 crate 類型的 crate 中。這么做出于復(fù)雜的技術(shù)原因,將來我們希望能夠消除這些限制。使用這些宏需采用類似示例 19-29 所示的代碼形式,其中 some_attribute
是一個(gè)使用特定宏的占位符。
文件名: src/lib.rs
use proc_macro;
#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {
}
示例 19-29: 一個(gè)使用過程宏的例子
定義過程宏的函數(shù)接收一個(gè) TokenStream 作為輸入并生成 TokenStream 作為輸出。TokenStream
是定義于proc_macro
crate里代表一系列token的類型,Rust默認(rèn)攜帶了proc_macro
crate。 這就是宏的核心:宏所處理的源代碼組成了輸入 TokenStream
,宏生成的代碼是輸出 TokenStream
。函數(shù)上還有一個(gè)屬性;這個(gè)屬性指明了我們創(chuàng)建的過程宏的類型。在同一 crate 中可以有多種的過程宏。
讓我們看看不同種類的程序宏。 我們將從一個(gè)自定義的派生宏開始,然后解釋使其他形式不同的小差異。
讓我們創(chuàng)建一個(gè) hello_macro
crate,其包含名為 HelloMacro
的 trait 和關(guān)聯(lián)函數(shù) hello_macro
。不同于讓 crate 的用戶為其每一個(gè)類型實(shí)現(xiàn) HelloMacro
trait,我們將會(huì)提供一個(gè)過程式宏以便用戶可以使用 #[derive(HelloMacro)]
注解他們的類型來得到 hello_macro
函數(shù)的默認(rèn)實(shí)現(xiàn)。該默認(rèn)實(shí)現(xiàn)會(huì)打印 Hello, Macro! My name is TypeName!
,其中 TypeName
為定義了 trait 的類型名。換言之,我們會(huì)創(chuàng)建一個(gè) crate,使程序員能夠?qū)戭愃剖纠?19-30 中的代碼。
文件名: src/main.rs
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro();
}
示例 19-30: crate 用戶所寫的能夠使用過程式宏的代碼
運(yùn)行該代碼將會(huì)打印 Hello, Macro! My name is Pancakes!
第一步是像下面這樣新建一個(gè)庫 crate:
$ cargo new hello_macro --lib
接下來,會(huì)定義 HelloMacro
trait 以及其關(guān)聯(lián)函數(shù):
文件名: src/lib.rs
pub trait HelloMacro {
fn hello_macro();
}
現(xiàn)在有了一個(gè)包含函數(shù)的 trait 。此時(shí),crate 用戶可以實(shí)現(xiàn)該 trait 以達(dá)到其期望的功能,像這樣:
use hello_macro::HelloMacro;
struct Pancakes;
impl HelloMacro for Pancakes {
fn hello_macro() {
println!("Hello, Macro! My name is Pancakes!");
}
}
fn main() {
Pancakes::hello_macro();
}
然而,他們需要為每一個(gè)他們想使用 hello_macro
的類型編寫實(shí)現(xiàn)的代碼塊。我們希望為其節(jié)約這些工作。
另外,我們也無法為 hello_macro
函數(shù)提供一個(gè)能夠打印實(shí)現(xiàn)了該 trait 的類型的名字的默認(rèn)實(shí)現(xiàn):Rust 沒有反射的能力,因此其無法在運(yùn)行時(shí)獲取類型名。我們需要一個(gè)在編譯時(shí)生成代碼的宏。
下一步是定義過程式宏。在編寫本部分時(shí),過程式宏必須在其自己的 crate 內(nèi)。該限制最終可能被取消。構(gòu)造 crate 和其中宏的慣例如下:對(duì)于一個(gè) foo
的包來說,一個(gè)自定義的派生過程宏的包被稱為 foo_derive
。在 hello_macro
項(xiàng)目中新建名為 hello_macro_derive
的包。
$ cargo new hello_macro_derive --lib
由于兩個(gè) crate 緊密相關(guān),因此在 hello_macro
包的目錄下創(chuàng)建過程式宏的 crate。如果改變?cè)?nbsp;hello_macro
中定義的 trait ,同時(shí)也必須改變?cè)?nbsp;hello_macro_derive
中實(shí)現(xiàn)的過程式宏。這兩個(gè)包需要分別發(fā)布,編程人員如果使用這些包,則需要同時(shí)添加這兩個(gè)依賴并將其引入作用域。我們也可以只用 hello_macro
包而將 hello_macro_derive
作為一個(gè)依賴,并重新導(dǎo)出過程式宏的代碼。但現(xiàn)在我們組織項(xiàng)目的方式使編程人員在無需 derive
功能時(shí)也能夠單獨(dú)使用 hello_macro
。
我們需要聲明 hello_macro_derive
crate 是過程宏(proc-macro) crate。我們還需要 syn
和 quote
crate 中的功能,正如你即將看到的,需要將他們加到依賴中。將下面的代碼加入到 hello_macro_derive
的 Cargo.toml 文件中。
文件名: hello_macro_derive/Cargo.toml
[lib]
proc-macro = true
[dependencies]
syn = "1.0"
quote = "1.0"
為定義一個(gè)過程式宏,請(qǐng)將示例 19-31 中的代碼放在 hello_macro_derive
crate 的 src/lib.rs 文件里面。注意這段代碼在我們添加 impl_hello_macro
函數(shù)的定義之前是無法編譯的。
文件名: hello_macro_derive/src/lib.rs
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_hello_macro(&ast)
}
示例 19-31: 大多數(shù)過程式宏處理 Rust 代碼時(shí)所需的代碼
注意我們將代碼分成了hello_macro_derive
和 impl_macro_derive
兩個(gè)函數(shù),前者負(fù)責(zé)解析 TokenStream
,后者負(fù)責(zé)轉(zhuǎn)換語法樹:這使得編寫過程宏更方便。幾乎你看到或者創(chuàng)建的每一個(gè)過程宏的外部函數(shù)(這里是hello_macro_derive
)中的代碼都跟這里是一樣的。你放入內(nèi)部函數(shù)(這里是impl_macro_derive
)中的代碼根據(jù)你的過程宏的設(shè)計(jì)目的會(huì)有所不同。
現(xiàn)在,我們已經(jīng)引入了三個(gè)新的 crate:proc_macro
、 syn
和 quote
。Rust 自帶 proc_macro
crate,因此無需將其加到 Cargo.toml 文件的依賴中。proc_macro
crate 是編譯器用來讀取和操作我們 Rust 代碼的 API。
syn
crate 將字符串中的 Rust 代碼解析成為一個(gè)可以操作的數(shù)據(jù)結(jié)構(gòu)。quote
則將 syn
解析的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換回 Rust 代碼。這些 crate 讓解析任何我們所要處理的 Rust 代碼變得更簡(jiǎn)單:為 Rust 編寫整個(gè)的解析器并不是一件簡(jiǎn)單的工作。
當(dāng)用戶在一個(gè)類型上指定 #[derive(HelloMacro)]
時(shí),hello_macro_derive
函數(shù)將會(huì)被調(diào)用。因?yàn)槲覀円呀?jīng)使用 proc_macro_derive
及其指定名稱HelloMacro
對(duì) hello_macro_derive
函數(shù)進(jìn)行了注解,指定名稱HelloMacro
就是 trait 名,這是大多數(shù)過程宏遵循的習(xí)慣。
該函數(shù)首先將來自 TokenStream
的 input
轉(zhuǎn)換為一個(gè)我們可以解釋和操作的數(shù)據(jù)結(jié)構(gòu)。這正是 syn
派上用場(chǎng)的地方。syn
中的 parse
函數(shù)獲取一個(gè) TokenStream
并返回一個(gè)表示解析出 Rust 代碼的 DeriveInput
結(jié)構(gòu)體。示例 19-32 展示了從字符串 struct Pancakes;
中解析出來的 DeriveInput
結(jié)構(gòu)體的相關(guān)部分:
DeriveInput {
// --snip--
ident: Ident {
ident: "Pancakes",
span: #0 bytes(95..103)
},
data: Struct(
DataStruct {
struct_token: Struct,
fields: Unit,
semi_token: Some(
Semi
)
}
)
}
示例 19-32: 解析示例 19-30 中帶有宏屬性的代碼時(shí)得到的 DeriveInput
實(shí)例
該結(jié)構(gòu)體的字段展示了我們解析的 Rust 代碼是一個(gè)類單元結(jié)構(gòu)體,其 ident
( identifier,表示名字)為 Pancakes
。該結(jié)構(gòu)體里面有更多字段描述了所有類型的 Rust 代碼,查閱 syn
中 DeriveInput
的文檔 以獲取更多信息。
很快我們將定義 impl_hello_macro
函數(shù),其用于構(gòu)建所要包含在內(nèi)的 Rust 新代碼。但在此之前,注意其輸出也是 TokenStream
。所返回的 TokenStream
會(huì)被加到我們的 crate 用戶所寫的代碼中,因此,當(dāng)用戶編譯他們的 crate 時(shí),他們會(huì)通過修改后的 TokenStream
獲取到我們所提供的額外功能。
你可能也注意到了,當(dāng)調(diào)用 syn::parse
函數(shù)失敗時(shí),我們用 unwrap
來使 hello_macro_derive
函數(shù) panic。在錯(cuò)誤時(shí) panic 對(duì)過程宏來說是必須的,因?yàn)?nbsp;proc_macro_derive
函數(shù)必須返回 TokenStream
而不是 Result
,以此來符合過程宏的 API。這里選擇用 unwrap
來簡(jiǎn)化了這個(gè)例子;在生產(chǎn)代碼中,則應(yīng)該通過 panic!
或 expect
來提供關(guān)于發(fā)生何種錯(cuò)誤的更加明確的錯(cuò)誤信息。
現(xiàn)在我們有了將注解的 Rust 代碼從 TokenStream
轉(zhuǎn)換為 DeriveInput
實(shí)例的代碼,讓我們來創(chuàng)建在注解類型上實(shí)現(xiàn) HelloMacro
trait 的代碼,如示例 19-33 所示。
文件名: hello_macro_derive/src/lib.rs
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}!", stringify!(#name));
}
}
};
gen.into()
}
示例 19-33: 使用解析過的 Rust 代碼實(shí)現(xiàn) HelloMacro
trait
我們得到一個(gè)包含以 ast.ident
作為注解類型名字(標(biāo)識(shí)符)的 Ident
結(jié)構(gòu)體實(shí)例。示例 19-32 中的結(jié)構(gòu)體表明當(dāng) impl_hello_macro
函數(shù)運(yùn)行于示例 19-30 中的代碼上時(shí) ident
字段的值是 "Pancakes"
。因此,示例 19-33 中 name
變量會(huì)包含一個(gè) Ident
結(jié)構(gòu)體的實(shí)例,當(dāng)打印時(shí),會(huì)是字符串 "Pancakes"
,也就是示例 19-30 中結(jié)構(gòu)體的名稱。
quote!
宏能讓我們編寫希望返回的 Rust 代碼。quote!
宏執(zhí)行的直接結(jié)果并不是編譯器所期望的所以需要轉(zhuǎn)換為 TokenStream
。為此需要調(diào)用 into
方法,它會(huì)消費(fèi)這個(gè)中間表示(intermediate representation,IR)并返回所需的 TokenStream
類型值。
這個(gè)宏也提供了一些非??岬哪0鍣C(jī)制;我們可以寫 #name
,然后 quote!
會(huì)以名為 name
的變量值來替換它。你甚至可以做一些類似常用宏那樣的重復(fù)代碼的工作。查閱 quote
crate 的文檔 來獲取詳盡的介紹。
我們期望我們的過程式宏能夠?yàn)橥ㄟ^ #name
獲取到的用戶注解類型生成 HelloMacro
trait 的實(shí)現(xiàn)。該 trait 的實(shí)現(xiàn)有一個(gè)函數(shù) hello_macro
,其函數(shù)體包括了我們期望提供的功能:打印 Hello, Macro! My name is
和注解的類型名。
此處所使用的 stringify!
為 Rust 內(nèi)置宏。其接收一個(gè) Rust 表達(dá)式,如 1 + 2
, 然后在編譯時(shí)將表達(dá)式轉(zhuǎn)換為一個(gè)字符串常量,如 "1 + 2"
。這與 format!
或 println!
是不同的,它計(jì)算表達(dá)式并將結(jié)果轉(zhuǎn)換為 String
。有一種可能的情況是,所輸入的 #name
可能是一個(gè)需要打印的表達(dá)式,因此我們用 stringify!
。stringify!
也能通過在編譯時(shí)將 #name
轉(zhuǎn)換為字符串來節(jié)省內(nèi)存分配。
此時(shí),cargo build
應(yīng)該都能成功編譯 hello_macro
和 hello_macro_derive
。我們將這些 crate 連接到示例 19-30 的代碼中來看看過程宏的行為!在 projects 目錄下用 cargo new pancakes
命令新建一個(gè)二進(jìn)制項(xiàng)目。需要將 hello_macro
和 hello_macro_derive
作為依賴加到 pancakes
包的 Cargo.toml 文件中去。如果你正將 hello_macro
和 hello_macro_derive
的版本發(fā)布到 crates.io 上,其應(yīng)為常規(guī)依賴;如果不是,則可以像下面這樣將其指定為 path
依賴:
hello_macro = { path = "../hello_macro" }
hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }
把示例 19-30 中的代碼放在 src/main.rs ,然后執(zhí)行 cargo run
:其應(yīng)該打印 Hello, Macro! My name is Pancakes!
。其包含了該過程宏中 HelloMacro
trait 的實(shí)現(xiàn),而無需 pancakes
crate 實(shí)現(xiàn)它;#[derive(HelloMacro)]
增加了該 trait 實(shí)現(xiàn)。
接下來,讓我們探索一下其他類型的過程宏與自定義派生宏有何區(qū)別。
類屬性宏與自定義派生宏相似,不同的是 derive
屬性生成代碼,它們(類屬性宏)能讓你創(chuàng)建新的屬性。它們也更為靈活;derive
只能用于結(jié)構(gòu)體和枚舉;屬性還可以用于其它的項(xiàng),比如函數(shù)。作為一個(gè)使用類屬性宏的例子,可以創(chuàng)建一個(gè)名為 route
的屬性用于注解 web 應(yīng)用程序框架(web application framework)的函數(shù):
#[route(GET, "/")]
fn index() {
#[route]
屬性將由框架本身定義為一個(gè)過程宏。其宏定義的函數(shù)簽名看起來像這樣:
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
這里有兩個(gè) TokenStream
類型的參數(shù);第一個(gè)用于屬性內(nèi)容本身,也就是 GET, "/"
部分。第二個(gè)是屬性所標(biāo)記的項(xiàng):在本例中,是 fn index() {}
和剩下的函數(shù)體。
除此之外,類屬性宏與自定義派生宏工作方式一致:創(chuàng)建 proc-macro
crate 類型的 crate 并實(shí)現(xiàn)希望生成代碼的函數(shù)!
類函數(shù)(Function-like)宏的定義看起來像函數(shù)調(diào)用的宏。類似于 macro_rules!
,它們比函數(shù)更靈活;例如,可以接受未知數(shù)量的參數(shù)。然而 macro_rules!
宏只能使用之前 “使用 macro_rules! 的聲明宏用于通用元編程” 介紹的類匹配的語法定義。類函數(shù)宏獲取 TokenStream
參數(shù),其定義使用 Rust 代碼操縱 TokenStream
,就像另兩種過程宏一樣。一個(gè)類函數(shù)宏例子是可以像這樣被調(diào)用的 sql!
宏:
let sql = sql!(SELECT * FROM posts WHERE id=1);
這個(gè)宏會(huì)解析其中的 SQL 語句并檢查其是否是句法正確的,這是比 macro_rules!
可以做到的更為復(fù)雜的處理。sql!
宏應(yīng)該被定義為如此:
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
這類似于自定義派生宏的簽名:獲取括號(hào)中的 token,并返回希望生成的代碼。
好的!現(xiàn)在我們學(xué)習(xí)了 Rust 并不常用但在特定情況下你可能用得到的功能。我們介紹了很多復(fù)雜的主題,這樣若你在錯(cuò)誤信息提示或閱讀他人代碼時(shí)遇到他們,至少可以說之前已經(jīng)見過這些概念和語法了。你可以使用本章作為一個(gè)解決方案的參考。
接下來,我們將再開始一個(gè)項(xiàng)目,將本書所學(xué)的所有內(nèi)容付與實(shí)踐!
更多建議: