欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

rust聲明式宏的實現(xiàn)

 更新時間:2023年12月07日 11:03:51   作者:zy010101  
聲明式宏使得你能夠?qū)懗鲱愃?match?表達(dá)式的東西,來操作你所提供的?Rust代碼,它使用你提供的代碼來生成用于替換宏調(diào)用的代碼,感興趣的可以了解一下

在 rust 中,我們一開始就在使用宏,例如 println!, vec!, assert_eq! 等。看起來宏和函數(shù)在使用時只是多了一個 !。實際上這些宏都是聲明式宏(也叫示例宏或macro_rules!),rust 還支持過程宏,過程宏為我們提供了強大的元編程工具。

聲明式宏

聲明式宏類似于 match 匹配。它可以將表達(dá)式的結(jié)果與多個模式進(jìn)行匹配。一旦匹配成功,那么該模式相關(guān)聯(lián)的代碼將被展開。和 match 不同的是,宏里的值是一段 rust 源代碼。所有這些都發(fā)生在編譯期,并沒有運行期的性能損耗。下面是一個例子:

// 聲明一個add宏
macro_rules! add {
    ($a: expr, $b: expr) => {
        $a + $b
    };
}

fn main() {
    let a = 10;
    let b = 22;

    let _res = add!(a, b);
    let _res = add!(a+1, b);
    let _res = add!(a*2, b+3);
}

我們需要一個類似于 GCC -E 的方式來查看一下預(yù)處理階段之后的代碼。cargo-expand 正好提供了相應(yīng)的功能。使用 cargo 安裝 cargo-expand 即可。

cargo install cargo-expand

安裝 cargo-expand 之后,可以使用 cargo expand 命令來查看聲明式宏是如何被展開的。上面的代碼在執(zhí)行cargo expand之后輸出如下所示:

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
fn main() {
    let a = 10;
    let b = 22;
    let _res = a + b;
    let _res = a + 1 + b;
    let _res = a * 2 + (b + 3);
}

可以看到,每一個 _res 的右邊都被展開了,并且如果傳入的參數(shù)是一個表達(dá)式,則會將整個表達(dá)式作為一個整體傳遞給宏。這就是某些地方提到的“Hygienic Macros”(有些地方也翻譯為衛(wèi)生宏,翻譯的很抽象)。最后一行代碼中傳入的b+3被當(dāng)做了一個整體。如果是在C/C++中,不會自動將表達(dá)式作為整體,而是直接進(jìn)行字符串替換。而 Rust 編譯器會自動處理變量名和作用域,確保宏展開后的代碼不會引入未預(yù)料的變量沖突。下面是一個C/C++中使用宏的例子。

#include<stdio.h>
#define ADD(a, b) a + b;

int main() {
    int a = 10;
    int b = 22;
    int _res = ADD(a, b)
    _res = ADD(a+1, b)
    _res = ADD(a*2, b+3)
} 

同樣,我們使用 gcc -E main.c 來獲取預(yù)處理之后的代碼。由于展開之后的代碼非常得多,我們只放上 main 函數(shù)中展開的部分。

int main() {
    int a = 10;
    int b = 22;
    int _res = a + b;
    _res = a+1 + b;
    _res = a*2 + b+3;
}

可以看到,調(diào)用的代碼展開之后,并沒有將 b+3 作為一個整體來處理,而是簡單的進(jìn)行替換。因此,我們在 C/C++ 中編寫宏要特別注意,宏參數(shù)在使用的時候必須加上括號?,F(xiàn)在我們來修復(fù)上面 C/C++ 代碼中的宏。

#include<stdio.h>
#define ADD(a, b) (a) + (b);

int main() {
    int a = 10;
    int b = 22;
    int _res = ADD(a, b)
    _res = ADD(a+1, b)
    _res = ADD(a*2, b+3)
} 

這樣,我們在使用宏的時候,就避免了意外結(jié)果的發(fā)生。這樣展開之后的代碼如下所示:

int main() {
    int a = 10;
    int b = 22;
    int _res = (a) + (b);
    _res = (a+1) + (b);
    _res = (a*2) + (b+3);
}

我們接著來定義我們自己的 my_vec! 宏, 來對聲明式宏的相關(guān)語法做一個解釋。

macro_rules! my_vec {
    // 匹配 my_vec![]
    () => {
        std::vec::Vec::new()
    };
    // 匹配 my_vec![1,2,3]
    ($($el:expr), *) => {
        // 這段代碼需要用{}包裹起來,因為宏需要展開,這樣能保證作用域正常,不影響外部。這也是rust的宏是 Hygienic Macros 的體現(xiàn)。 
        // 而 C/C++ 的宏不強制要求,但是如果遇到代碼片段,在 C/C++ 中也應(yīng)該使用{}包裹起來。
        {
            let mut v = std::vec::Vec::new();
            $(v.push($el);)*
            v
        }
    };
    // 匹配 my_vec![1; 3]
    ($el:expr; $n:expr) => {
        std::vec::from_elem($el, $n)
    };
}
  • 由于宏要在調(diào)用的地方展開,我們無法預(yù)測調(diào)用者的環(huán)境是否已經(jīng)做了相關(guān)的 use,所以我們使用的代碼最好帶著完整的命名空間。

  • 在聲明宏中,條件捕獲的參數(shù)使用 $ 開頭的標(biāo)識符來聲明。每個參數(shù)都需要提供類型,這里 expr 代表表達(dá)式,所以 $el:expr 是說把匹配到的表達(dá)式命名為 $el$(...),* 告訴編譯器可以匹配任意多個以逗號分隔的表達(dá)式,然后捕獲到的每一個表達(dá)式可以用 $el 來訪問。由于匹配的時候匹配到一個 $(...)* (我們可以不管分隔符),在執(zhí)行的代碼塊中,我們也要相應(yīng)地使用 $(...)* 展開。所以這句 $(v.push($el);)* 相當(dāng)于匹配出多少個 $el 就展開多少句 push 語句。
    反復(fù)捕獲反復(fù)捕獲的一般形式是$ ( ... ) sep rep,

     $ 是字面上的美元符號標(biāo)記
     ( ... ) 是被反復(fù)匹配的模式,由小括號包圍。
     sep 是可選的分隔標(biāo)記。它不能是括號或者反復(fù)操作符 rep。常用例子有 , 和 ; 。
     rep 是必須的重復(fù)操作符。當(dāng)前可以是:
     1. ?:表示最多一次重復(fù),所以此時不能前跟分隔標(biāo)記。
     2. *:表示零次或多次重復(fù)。
     3. +:表示一次或多次重復(fù)。
    
  • 如果傳入用冒號分隔的兩個表達(dá)式,那么會用 from_element 構(gòu)建 Vec。

我們來使用一下自定義的 my_vec! 宏

let mut v = my_vec!();
v.push(1);
println!("{:?}", v);
let v = my_vec![1, 2, 3, 4, 5];
println!("{:?}", v);
let v = my_vec!{1; 3};
println!("{:?}", v);

我們在使用宏的時候,可以使用(), [], {},都是可以的。但是一般都是按照約定成俗的方式來使用。例如:vec![1,2,3],而不是使用 vec!{1,2,3}

這段宏調(diào)用,展開以后,如下所示:

let mut v = std::vec::Vec::new();
v.push(1);
{
    ::std::io::_print(format_args!("{0:?}\n", v));
};
let v = {
    let mut v = std::vec::Vec::new();
    v.push(1);
    v.push(2);
    v.push(3);
    v.push(4);
    v.push(5);
    v
};
{
    ::std::io::_print(format_args!("{0:?}\n", v));
};
let v = std::vec::from_elem(1, 3);
{
    ::std::io::_print(format_args!("{0:?}\n", v));
};

可以看到,let v = my_vec![1, 2, 3, 4, 5]; 被展開為

let v = {
    let mut v = std::vec::Vec::new();
    v.push(1);
    v.push(2);
    v.push(3);
    v.push(4);
    v.push(5);
    v
};

它帶上了我們在宏定義中的{},另外我們注意到println! 宏也被展開了, 但是并沒有完全展開,其中還包含了一個format_args! 宏,我們來看一下,是否和println宏的定義一樣。

// println宏的定義
macro_rules! println {
    () => {
        $crate::print!("\n")
    };
    ($($arg:tt)*) => {{
        $crate::io::_print($crate::format_args_nl!($($arg)*));
    }};
}

可以看到,println帶有參數(shù)將會使用 format_args_nl! 宏,但是expand確是 format_args 宏。大概可能是因為文檔中說format_args_nl宏是nightly模式下的吧!并沒有完全展開是因為該宏是內(nèi)置宏(rustc_builtin_macro)。

在使用聲明宏時,我們需要為參數(shù)明確類型,剛才的例子都是使用的expr,其實還可以使用下面這些:

  • item,比如一個函數(shù)、結(jié)構(gòu)體、模塊等。
  • block,代碼塊。比如一系列由花括號包裹的表達(dá)式和語句。
  • stmt,語句。比如一個賦值語句。
  • pat,模式。
  • expr,表達(dá)式。剛才的例子使用過了。
  • ty,類型。比如 Vec。
  • ident,標(biāo)識符。比如一個變量名。
  • path,路徑。比如:foo、::std::mem::replace、transmute::<_, int>。 meta,元數(shù)據(jù)。一般是在 #[...]`` 和 #![…]`` 屬性內(nèi)部的數(shù)據(jù)。
  • tt,單個的 token 樹。
  • vis,可能為空的一個 Visibility 修飾符。比如 pub、pub(crate)

聲明式宏還算比較簡單。它可以幫助我們解決一些問題。

  • 代碼重復(fù):聲明式宏可以幫助消除代碼中的冗余,通過將重復(fù)的代碼邏輯抽象成宏,從而減少代碼量并提高代碼的可讀性和維護性。
  • 代碼模板化:宏可以用于定義代碼模板,允許在編譯時根據(jù)不同的參數(shù)生成特定的代碼片段,從而實現(xiàn)代碼的泛化和重用。
  • 實現(xiàn)函數(shù)重載,宏可以匹配多種模式的參數(shù)來實現(xiàn)函數(shù)重載。

宏的缺點

宏目前的編寫無法得到IDE很好的支持,另外一點就是如無必要,就不要編寫宏。如果要編寫,那么盡量編寫聲明式宏,而不是過程宏。

  • 宏編寫復(fù)雜:過程宏的編寫可能相對復(fù)雜,特別是對于復(fù)雜的語法分析和代碼生成任務(wù),編寫和調(diào)試過程宏可能需要更多的時間和精力。
  • 可讀性下降:宏可能會導(dǎo)致代碼的可讀性下降,特別是在宏的展開代碼復(fù)雜或嵌套層級較多時,代碼可讀性可能變差。
  • 不利于錯誤檢查:宏展開發(fā)生在編譯期間,因此錯誤信息可能不夠明確和直觀,難以定位宏展開后的具體錯誤位置。
  • 難以調(diào)試:宏展開過程對于開發(fā)者不是透明的,因此在調(diào)試過程中可能會遇到難以解決的問題。

參考資料

到此這篇關(guān)于rust聲明式宏的實現(xiàn)的文章就介紹到這了,更多相關(guān)rust聲明式宏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • Rust調(diào)用Windows API 如何獲取正在運行的全部進(jìn)程信息

    Rust調(diào)用Windows API 如何獲取正在運行的全部進(jìn)程信息

    本文介紹了如何使用Rust調(diào)用WindowsAPI獲取正在運行的全部進(jìn)程信息,通過引入winapi依賴并添加相應(yīng)的features,可以實現(xiàn)對不同API集的調(diào)用,本文通過實例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • rust標(biāo)準(zhǔn)庫std::env環(huán)境相關(guān)的常量

    rust標(biāo)準(zhǔn)庫std::env環(huán)境相關(guān)的常量

    在本章節(jié)中, 我們探討了Rust處理命令行參數(shù)的常見的兩種方式和處理環(huán)境變量的兩種常見方式, 拋開Rust的語法, 實際上在命令行參數(shù)的處理方式上, 與其它語言大同小異, 可能影響我們習(xí)慣的也就只剩下語法,本文介紹rust標(biāo)準(zhǔn)庫std::env的相關(guān)知識,感興趣的朋友一起看看吧
    2024-03-03
  • RUST語言函數(shù)的定義與調(diào)用方法

    RUST語言函數(shù)的定義與調(diào)用方法

    定義一個RUST函數(shù)使用fn關(guān)鍵字,下面通過本文給大家介紹RUST語言函數(shù)的定義與調(diào)用方法,感興趣的朋友跟隨小編一起看看吧
    2024-04-04
  • 一文弄懂rust生命周期

    一文弄懂rust生命周期

    生命周期是Rust語言中的一個概念,用于決內(nèi)存安全問題,本文主要介紹了一文弄懂rust生命周期,具有一定的參考價值,感興趣的可以了解一下
    2023-12-12
  • 詳解thiserror庫在Rust中的使用

    詳解thiserror庫在Rust中的使用

    在編程中,錯誤處理是一個至關(guān)重要的部分,在Rust中,我們經(jīng)常使用Result和Option類型來進(jìn)行錯誤處理,但有時,我們需要創(chuàng)建自定義的錯誤類型,這就是thiserror庫發(fā)揮作用的地方,可以極大的簡化代碼,所以本文就給大家介紹一下如何使用thiserror
    2023-08-08
  • 關(guān)于Rust命令行參數(shù)解析以minigrep為例

    關(guān)于Rust命令行參數(shù)解析以minigrep為例

    本文介紹了如何使用Rust的std::env::args函數(shù)來解析命令行參數(shù),并展示了如何將這些參數(shù)存儲在變量中,隨后,提到了處理文件和搜索邏輯的步驟,包括讀取文件內(nèi)容、搜索匹配項和輸出搜索結(jié)果,最后,總結(jié)了Rust標(biāo)準(zhǔn)庫在命令行參數(shù)處理中的便捷性和社區(qū)資源的支持
    2025-02-02
  • rust中trait的使用方法詳解

    rust中trait的使用方法詳解

    trait用中文來講就是特征,它就是一個標(biāo)記,只不過這個標(biāo)記被用在特定的地方,也就是類型參數(shù)的后面,下面我們就來學(xué)習(xí)一下trait的具體使用方法吧
    2023-12-12
  • Rust中的&和ref使用解讀

    Rust中的&和ref使用解讀

    在Rust中,`&`和`ref`都可以用來定義指針,但它們的使用位置不同,`&`通常放在等號右邊,而`ref`放在左邊,`&`主要用于函數(shù)參數(shù)和模式匹配中,而`ref`主要用于模式匹配中,Rust通過`&`和`ref`提供了靈活的指針操作,使得代碼更加安全和高效
    2025-02-02
  • Rust anyhow 簡明示例教程

    Rust anyhow 簡明示例教程

    anyhow 是 Rust 中的一個庫,旨在提供靈活的、具體的錯誤處理能力,建立在 std::error::Error 基礎(chǔ)上,主要用于那些需要簡單錯誤處理的應(yīng)用程序和原型開發(fā)中,本文給大家分享Rust anyhow 簡明教程,一起看看吧
    2024-06-06
  • 使用vscode配置Rust運行環(huán)境全過程

    使用vscode配置Rust運行環(huán)境全過程

    VS Code對Rust有著較完備的支持,這篇文章主要給大家介紹了關(guān)于使用vscode配置Rust運行環(huán)境的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-06-06

最新評論