深入瞭解 Rust 過程宏 - 1
你是否想過 Rust 的過程宏是如何工作的?在這個系列文章中,我們將深入瞭解 Rust 過程宏細節!我們將看看 Rust 過程宏的基礎知識,並深入到過程宏的細節中,密切關注它們的 API。
宏
宏在 Rust 中隨處可見,但有些編程語言根本不使用宏。讓我們來研究一下什麼是宏以及它們有什麼作用。
宏有三個主要用途:
-
它允許你編寫創建其他代碼的代碼。
-
它允許你使用自定義結構擴展語言語法。
-
它可以幫助你減少樣板代碼的數量。
宏如何生成新代碼
我們來創建一個向量,然後輸入三個數字。最簡單的代碼如下:
fn main() {
let mut a = Vec::new();
a.push(1);
a.push(2);
a.push(3);
}
我們可以使用標準庫的 vec 宏重寫這段代碼:
fn main() {
let a = vec![1, 2, 3];
}
vec ![1,2,3] 部分是調用了 vec 宏。這個宏是一個聲明宏,它的聲明如下 (簡化):
macro_rules! vec {
($($x:expr),+) => ({
let mut v = Vec::new();
$( v.push($x); )+
v
});
}
在這裏,“$($x:expr),+” 被稱爲宏模式。宏調用的主體是 [1,2,3],匹配如下模式:
元變量按如下方式放入擴展模板:
注意,宏展開代碼 (右邊) 看起來非常像我們在本例中使用的初始代碼。實際上,當擴展代碼替換宏調用時,它會將原始代碼轉換爲以下代碼:
fn main() {
let a = {
let mut v = Vec::new();
v.push(1);
v.push(2);
v.push(3);
v
};
}
然後,編譯器可以像往常一樣處理這些代碼。
宏如何創建新的語法
讓我們來看看來自 yew 庫的過程宏 html,它有助於在 Rust 中編寫 web 前端。下面是一個如何調用這個宏的例子:
use yew::html;
html! {
<div>
<div>
{ "Hello, World!" }
</div>
</div>
}
宏調用完全不像 Rust,不是嗎?但是 html 將其調用的代碼解析爲一種類似於 html 的語言,並生成一個稱爲虛擬 DOM 的層次結構。得到的擴展代碼是純 Rust,然後可以由 rustc 編譯。
VTag::new(
"div",
vec![VTag::new("div", ...)],
);
這就是宏如何將另一種語言嵌入 Rust 的。
注意,過程宏中不允許使用被禁止的符號,一個過程宏只能包含在 Rust 中已經允許的標記。
宏如何幫助減少樣板代碼
爲了說明這一點,我們來看一下示例。通常情況下,需要執行的 Trait 有很多:
struct Foo { x: i32, y: i32 }
impl Copy for Foo { ... }
impl Clone for Foo { ... }
impl Ord for Foo { ... }
impl PartialOrd for Foo { ... }
impl Eq for Foo { ... }
impl PartialEq for Foo { ... }
impl Debug for Foo { ... }
impl Hash for Foo { ... }
這裏可以派上用場的是派生,這是一個過程宏。可以使用 rustc 中的派生宏重寫上述特徵:
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug, Hash, Default)]
struct Foo { x: i32, y: i32 }
過程宏
本質上,過程宏是在編譯時執行的 Rust 函數。這樣的函數屬於一個特殊的 crate,在 Cargo.toml 中用 proc-macro 標記。這看起來像這樣:
[package]
name = "my-proc-macro"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
過程宏的類型
過程宏有三種類型:
- 函數宏: 這些宏使用 #[proc_macro] 屬性聲明,並像常規函數一樣調用,類似於聲明式宏。
#[proc_macro]
pub fn foo(body: TokenStream) -> TokenStream { ... }
…
foo!( foo bar baz );
- 派生宏:這些宏使用 #[proc_macro_derive] 屬性聲明,並在 #[derive] 中用於 struct 和 enum。
#[proc_macro_derive(Bar)]
pub fn bar(body: TokenStream) -> TokenStream { ... }
…
#[derive(Bar)]
struct S;
- 屬性宏:這些宏使用 #[proc_macro_attribute] 聲明。
#[proc_macro_attribute]
pub fn baz(
attr: TokenStream,
item: TokenStream
) -> TokenStream { ... }
…
#[baz]
fn some_item() {}
下一篇文章,我們將看看過程宏的 API 及如何編寫過程宏。
本文翻譯自:
https://blog.jetbrains.com/rust/2022/03/18/procedural-macros-under-the-hood-part-i/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/PXx5-CI9rFRog8TfJ1ZDeg