使用 macro_rules 編寫生產 Rust 宏!

banq發表於2024-07-19


如果您對宏不熟悉,那麼您對 ​​Rust 也不熟悉。

在本指南中,我們將揭秘支撐數十萬個 Rust 應用程式的三個宏。它們不是由巫師編寫的,而是由我們渴望成為的才華橫溢的 Rust 工程師編寫的。

這三個宏都是 "宣告式 "的,也就是用宏規則(macro_rules! 這些宏構成了 Rust 宏的主體,將標記作為輸入,進行模式匹配,並根據匹配結果輸出原始碼。 程式宏構成了宏家族的其他部分,涉及對標記流的程式設計操作。

一個簡單的 Rust 宏:assert_eq!

let a = 3;  
let b = 1 + 2;  
assert_eq!(a, b);
 
assert_eq!(a, b, <font>"we are testing addition with {} and {}", a, b);

assert_eq!支援兩種引數模式。

  • 第一種匹配兩個表示式,a, b並比較它們的值是否相等。
  • 第二種也接受格式字串和任意數量的引數進行插值。

如果任一匹配模式的相等性檢查失敗,assert_eq!則會產生panic。

因為我們有兩種引數模式,所以你正確地期望宏定義中有兩條規則:

#[macro_export]
macro_rules! assert_eq {
    ($left:expr, $right:expr $(,)?) => { ... };

    ($left:expr, $right:expr, $($arg:tt)+) => { ... };

}

  • 第一個宏:採用兩個表示式 - 等式的左邊和右邊 - 以及可選的尾隨逗號。
  • 第二個宏:規則 2 匹配同樣的兩個表示式,但也匹配與格式字串及其引數相對應的一個或多個TokenTree 令牌樹。

TokenTree :可以是單個標記(如 3 或 "hello"),也可以是由()、[] 或 {} 包含的任意數量的巢狀標記。

它們的擴充套件幾乎完全相同:

#[macro_export]
macro_rules! assert_eq {  
    ($left:expr, $right:expr $(,)?) => {  
        match (&$left, &$right) {  
            (left_val, right_val) => {

                if !(*left_val == *right_val) {

                    let kind = $crate::panicking::AssertKind::Eq;

                    <font>// ...<i>
                }  
            }  
        }  
    };  
    ($left:expr, $right:expr, $($arg:tt)+) => {  
        match (&$left, &$right) {  
            (left_val, right_val) => {  
                if !(*left_val == *right_val) {  
                    let kind = $crate::panicking::AssertKind::Eq;  
                   
// ...<i>
                }  
            }  
        }  
    };  
}

這兩條規則產生的程式碼都會對左手和右手錶達式進行求值,並將輸出結果的引用儲存在 left_val 和 right_val 3 中。 這樣,我們就不必多次求值 $left 或 $right。

請記住,雖然 assert_eq! 例子使用了原始型別,但 $left 和 $right 可能是昂貴的函式呼叫。

接下來,使用 PartialEq 4 進行簡單的相等性檢查。 當檢查失敗時,事情就變得有趣了。

兩種規則都將 $crate::panicking::AssertKind::Eq 賦值給變數 kind 5 。

Rust 很聰明,這個宏本地的 kind 變數不會與任何呼叫 assert_eq! 的名為 kind 的變數發生衝突,這就是所謂的宏衛生(macro hygiene)特性。

但為什麼要使用完全限定的模組路徑來指定 AssertKind::Eq 呢? 當你使用 #[macro_export] 匯出宏時,並不能保證在使用該宏時的作用域中會出現什麼。

assert_eq! 的使用者不應該被要求自己匯入這個實現細節,而完全限定的路徑可以避免這一點。

...
更多點選標題


 

相關文章