亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

在 rust 中,我們一開(kāi)始就在使用宏,例如 println!, vec!, assert_eq! 等??雌饋?lái)宏和函數(shù)在使用時(shí)只是多了一個(gè) !。實(shí)際上這些宏都是聲明式宏(也叫示例宏或macro_rules!),rust 還支持過(guò)程宏,過(guò)程宏為我們提供了強(qiáng)大的元編程工具。

聲明式宏

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

// 聲明一個(gè)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);
}

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

cargo install cargo-expand

安裝 cargo-expand 之后,可以使用 cargo expand 命令來(lái)查看聲明式宏是如何被展開(kāi)的。上面的代碼在執(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);
}

可以看到,每一個(gè) _res 的右邊都被展開(kāi)了,并且如果傳入的參數(shù)是一個(gè)表達(dá)式,則會(huì)將整個(gè)表達(dá)式作為一個(gè)整體傳遞給宏。這就是某些地方提到的“Hygienic Macros”(有些地方也翻譯為衛(wèi)生宏,翻譯的很抽象)。最后一行代碼中傳入的b+3被當(dāng)做了一個(gè)整體。如果是在C/C++中,不會(huì)自動(dòng)將表達(dá)式作為整體,而是直接進(jìn)行字符串替換。而 Rust 編譯器會(huì)自動(dòng)處理變量名和作用域,確保宏展開(kāi)后的代碼不會(huì)引入未預(yù)料的變量沖突。下面是一個(gè)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 來(lái)獲取預(yù)處理之后的代碼。由于展開(kāi)之后的代碼非常得多,我們只放上 main 函數(shù)中展開(kāi)的部分。

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

可以看到,調(diào)用的代碼展開(kāi)之后,并沒(méi)有將 b+3 作為一個(gè)整體來(lái)處理,而是簡(jiǎn)單的進(jìn)行替換。因此,我們?cè)?C/C++ 中編寫(xiě)宏要特別注意,宏參數(shù)在使用的時(shí)候必須加上括號(hào)?,F(xiàn)在我們來(lái)修復(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)
} 

這樣,我們?cè)谑褂煤甑臅r(shí)候,就避免了意外結(jié)果的發(fā)生。這樣展開(kāi)之后的代碼如下所示:

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

我們接著來(lái)定義我們自己的 my_vec! 宏, 來(lái)對(duì)聲明式宏的相關(guān)語(yǔ)法做一個(gè)解釋。

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

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

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

我們來(lái)使用一下自定義的 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);

我們?cè)谑褂煤甑臅r(shí)候,可以使用(), [], {},都是可以的。但是一般都是按照約定成俗的方式來(lái)使用。例如:vec![1,2,3],而不是使用 vec!{1,2,3}。

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

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]; 被展開(kāi)為

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
};

它帶上了我們?cè)诤甓x中的{},另外我們注意到println! 宏也被展開(kāi)了, 但是并沒(méi)有完全展開(kāi),其中還包含了一個(gè)format_args! 宏,我們來(lái)看一下,是否和println宏的定義一樣。

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

可以看到,println帶有參數(shù)將會(huì)使用 format_args_nl! 宏,但是expand確是 format_args 宏。大概可能是因?yàn)槲臋n中說(shuō)format_args_nl宏是nightly模式下的吧!并沒(méi)有完全展開(kāi)是因?yàn)樵摵晔莾?nèi)置宏(rustc_builtin_macro)。

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

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

聲明式宏還算比較簡(jiǎn)單。它可以幫助我們解決一些問(wèn)題。

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

宏的缺點(diǎn)

宏目前的編寫(xiě)無(wú)法得到IDE很好的支持,另外一點(diǎn)就是如無(wú)必要,就不要編寫(xiě)宏。如果要編寫(xiě),那么盡量編寫(xiě)聲明式宏,而不是過(guò)程宏。

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

參考資料

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

相關(guān)文章

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

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

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

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

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

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

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

    一文弄懂rust生命周期

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

    詳解thiserror庫(kù)在Rust中的使用

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

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

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

    rust中trait的使用方法詳解

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

    Rust中的&和ref使用解讀

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

    Rust anyhow 簡(jiǎn)明示例教程

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

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

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

最新評(píng)論