Rust 巨集中的片段指定符

https://anoopelias.github.io/posts/fragment-specifiers-in-rust-macros/

Rust 宏中的片段指定器

Rust 宏一開始讓我覺得很神秘,但一旦我開始了解,就發現它並不壞!

“片段指定器”是您在宏調用中可能看到的參數的“類型”。最常見的是 expr。就像下面這樣,

宏规则!加两個数字{
($num1:expr,$num2:expr)=>{
$num1 + $num2
};
}
fn main(){
let _result = add_two_numbers!(1,2);
}

請注意,調用以!結尾,比如 add_two_numbers!,這告訴我們這是一個宏調用。

在深入討論所有其他片段指定器之前,讓我們快速了解一下 Rust 宏以及何時使用它。

什麼是 Rust 宏?

Rust 宏是預處理器。基本上,它允許我們在編譯時添加或更改代碼。這個過程被稱為“宏展開”。如果讓 rust 編譯器展開上面的代碼,它會看起來像,

//略

Rust 宏的概念類似於 C 中的宏,因為他們都是預處理器,但它們在實現上有根本的不同。

在 C 中,宏展開發生在標記上,而在 Rust 中,它發生在 AST 上!這使得 rust 實現難以理解和使用,但也使它更有力量!我們會看到,

何時使用 Rust 宏?

正如你所想的,我上面給出的示例代碼(add_two_numbers) – 我們也可以使用函數達到相同的效果。就我個人而言,我至少看到兩種情況下,Rust 宏可能比函數更有用,

可變數量的參數。Rust 函數不支持此功能,因此,我們需要使用宏。最常見的例子是 println!宏。

//略

當我們想要對多種類型進行相同的實現。例如,看看這個。通過這種方式,我們可以避免創建 enums/traits,從而簡化客戶端的操作,同時也不會重複代碼。

我相信這還有更多。如果您知道,請告訴我!

片段指定器

“片段指定器”是用於參數的類型註釋的技術名稱。有時候我也看到片段指定器被稱為“標記型”。讓我們逐一看看它們的示例,

//略

注意:要查看這些宏展開的方式,您可以將此代碼複製並粘貼到 Rust Playground 中,然後選擇“TOOLS > Expand macros”。

expr

表達式示例

宏规则!是偶數{
($num:expr)=>(
匹配 $num % 2 {
0 => true,
1 => false,
_ => unreachable!()
}
);
}
fn main(){
let _result = is_even!(20 + 5);
}

ident

使用宏的標識符示例,

宏规则!創建功能{
($func_name:ident)=>{
fn $func_name(){
println!(“來自函數的問候:{}”,stringify!($func_name));
}
};
}

// 略

item

Item 是根級對象,如模塊、結構、特性、函數、impl block、使用聲明等。讓我們看一個與結構一起的示例,

//略

stmt

語句在產生副作用時與表達式不同,不一定需要返回任何東西,或者其返回的值被忽略。下面是一個使用 Statement 的宏示例,

//略

block

块是用大括号{}包裹的一行代碼。同時,它們也是表達式。就像下面這樣,

//略

ty

在這個例子中,我們可以將特定類型傳遞給宏,並根據類型生成代碼,像這樣,

//略

path

可能有其他用例,但我發現“路徑”在您想要將 enum 值傳遞給宏時特別有用。參看下面,

//略

pat

有了這個,我們可以將模式傳遞給一個宏,並且可以用作匹配表達式的 arm。例如,

//略

pat-param

當首次引入:pat 時,它不匹配 |,因此您可以在:pat-param 之后使用管道分隔符$pattern1 | $pattern2。但是,在 Rust 2021 中引入了一個重大變化,允許:pat 匹配其中的管道。因此不允許在:pat 之后使用管道分隔符,為了能夠使用管道作為宏參數分隔符,我們需要使用:pat-param。參見更多詳細信息。樣本代碼如下,

//略

Meta 指定器用於向宏發送#[xxx]類型的屬性。讓我們擴展我們的 create_struct 以支持屬性,

//略

literal

如果我們希望宏調用使用文字符號而不是表達式或變數,則可以使用文字符號指定器。

//略

vis

此指示符可用於將可見性修改器傳遞給宏。比如 pub 或 pub(crate)或甚至沒有!讓我們用這個來擴展我們的 create_struct!宏,

//略

lifetime

當您想要在具有生命周期參數的類型上創建 impl 函數時,生命周期片段指定器很有用。就像下面這樣,

//略

tt

也被稱為 Token Tree,這是所有片段指定器中最靈活的。token tree 是任意封裝在(),{}或[]中的任何東西。或它可以是一個單個標記。收買是,當我們無法將此宏參數用於某些片段指定器類型規則適用的地方時。我們仍然可以使 Token Tree 用於我們上面提到的許多示例,但例如,我們無法將 tt 類型參數用作匹配的 arm,我們絕對需要 pat。靈活性伴隨責任,因此在使用此指示符時要謹慎。盡量避免使用它,以便及早並且在正確的地方捕獲錯誤。

//略

總結

儘管我們討論了宏的參數,我們並沒有討論返回值的類型。一般來說,宏的返回值應該與宏調用的確切位置兼容。我們確實觸及了其中一些,例如,在表達式示例中,宏返回一個表達式,而在標識示例中,宏返回一個項目。也許我們可以編寫另一篇關於這個的博客。

這就是截至今天 Rust 支持的所有片段指定器的摘要。我們故意略過了一些宏特性,比如內部規則,但如果您感興趣,我強烈推薦 The Little Book of Rust Macros。

本文中的所有代碼都是在 Rust Playground 中進行測試的。如果您覺得有什麼不對,請隨時發送郵件。

感謝您的閱讀!

via Hacker News

February 5, 2024 at 09:53AM

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *