深入瞭解 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]
pub fn foo(body: TokenStream) -> TokenStream { ... }
…
foo!( foo bar baz );
#[proc_macro_derive(Bar)]
pub fn bar(body: TokenStream) -> TokenStream { ... }#[derive(Bar)]
struct S;
#[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