最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文弄懂rust聲明宏

 更新時(shí)間:2024年03月31日 11:27:17   作者:又耳筆記  
Rust支持兩種宏,一種是聲明宏,一種是過(guò)程宏,本文主要介紹了一文弄懂rust聲明宏,通過(guò)聲明宏可以減少一些樣板代碼,具有一定的參考價(jià)值,感興趣的可以了解一下

Rust支持兩種宏,一種是聲明宏,一種是過(guò)程宏,前者相較于后者還是比較簡(jiǎn)單的。本文主要是講解Rust元編程里的聲明宏,通過(guò)聲明宏可以減少一些樣板代碼,它是一個(gè)用代碼生成代碼的技術(shù)。

聲明宏的主要原理是通過(guò)匹配傳入的代碼然后替換成指定的代碼,因?yàn)樘鎿Q是發(fā)生在編譯器,所以rust的宏編程沒(méi)有任何運(yùn)行時(shí)的開銷,可以放心的用,不用擔(dān)心性能 :)。

快速入門

聲明宏不像過(guò)程宏那樣需要在單獨(dú)的包(package/crate)中定義,只需要使用macro_rules!就可以簡(jiǎn)單的定義一個(gè)聲明宏,一個(gè)簡(jiǎn)單的示例如下。

// https://youerning.top/post/rust-declarative-macros-tutorial/
macro_rules! add {
    ($a:expr, $b:expr) => {
        $a + $b
    };
}

fn main() {
    let sum = add!(1,2);
    println!("sum: {sum}");
}

輸出如下:

sum: 3

上面這個(gè)結(jié)果應(yīng)該不會(huì)讓人意外,你會(huì)發(fā)現(xiàn)聲明宏定義的那一段代碼和普通的match代碼非常相似,不同的在于變量前面多了個(gè)前綴$, 而且需要通過(guò)冒號(hào):注明變量的類型,這里的變量類型是expr,這是表達(dá)式的意思。

聲明宏語(yǔ)法

一個(gè)聲明宏大致可以分為三個(gè)部分

  • 聲明宏的名稱定義,比如例子中的add
  • 模式匹配部分, 比如例子中的($a:expr, $b:expr)
  • 聲明宏返回的部分, 也就是花括號(hào)被包裹的部分, 比如例子中的$a + $b

本文的開頭說(shuō)過(guò),過(guò)程宏的原理就是通過(guò)匹配傳入的代碼然后替換成指定的代碼, 所以上面的例子在編譯(展開)之后應(yīng)該會(huì)變成下面的代碼。

fn main() {
    let sum = 1 + 2;
    println!("sum: {sum}");
}

如果我們傳遞三個(gè)參數(shù)呢? 比如add!(1,2,3),那么它會(huì)在編譯的時(shí)候報(bào)以下錯(cuò)誤。

error: no rules expected the token `,`
 --> src\main.rs:8:23
  |
1 | macro_rules! add {
  | ---------------- when calling this macro
...
8 |     let sum = add!(1,2,3);
  |                       ^ no rules expected this token in macro call
  |
note: while trying to match meta-variable `$b:expr`
 --> src\main.rs:2:15
  |
2 |     ($a:expr, $b:expr)=>{
  |               ^^^^^^^

error: could not compile `declarative-macros` (bin "declarative-macros") due to previous error

其實(shí)這很好理解,我們的模式只能匹配兩個(gè)變量$a和$b, 但是add!(1,2,3)卻傳入了三個(gè)變量,所以匹配不了,那么就會(huì)報(bào)錯(cuò),因?yàn)檫@是不合法的語(yǔ)法。
那么,怎么匹配三個(gè)變量,或者是一個(gè)變量呢? 有兩個(gè)辦法,一是一一對(duì)應(yīng),二是使用重復(fù)的匹配方法。為了簡(jiǎn)單起見(jiàn),我們先使用比較笨的方法,代碼如下。

macro_rules! add {
    // 聲明宏的第一條匹配規(guī)則
    ($a: expr) => {
        $a
    };
    // 聲明宏的第二條匹配規(guī)則
    ($a:expr, $b:expr)=>{
        $a + $b
    };
    // 聲明宏的第三條匹配規(guī)則
    ($a:expr, $b:expr, $c: expr)=>{
        $a + $b
    };
}

fn main() {
    let sum = add!(1);
    println!("sum1: {sum}");
    let sum = add!(1,2);
    println!("sum2: {sum}");
    let sum = add!(1,2,3);
    println!("sum3: {sum}");
}

上面的代碼和快速入門的例子沒(méi)有太大的區(qū)別,主要的區(qū)別是之前的例子只有一個(gè)匹配規(guī)則,而新的例子有三條匹配規(guī)則,當(dāng)rust編譯代碼的時(shí)候,會(huì)將調(diào)用聲明宏的輸入?yún)?shù)從上至下依次匹配每條規(guī)則,當(dāng)匹配到就會(huì)停止匹配,然后返回對(duì)應(yīng)的代碼,這和rust的match模式匹配沒(méi)有太大的區(qū)別,唯一的區(qū)別可能是, 聲明宏使用;分隔不同的匹配模式,而match的不同匹配模式使用,分隔。

上面的代碼輸出如下:

sum1: 1
sum2: 3
sum3: 3

這樣的結(jié)果并不讓人意外,唯一讓人沮喪的是,每種情況都寫一個(gè)對(duì)應(yīng)的表達(dá)式的話,得累死去。

元變量

現(xiàn)在讓我們繼續(xù)看看rust的聲明宏支持哪些類型。

  • item: 條目,比如函數(shù)、結(jié)構(gòu)體、模組等。
  • block: 區(qū)塊(即由花括號(hào)包起的一些語(yǔ)句加上/或是一項(xiàng)表達(dá)式)。
  • stmt: 語(yǔ)句
  • pat: 模式
  • expr: 表達(dá)式
  • ty: 類型
  • ident: 標(biāo)識(shí)符
  • path: 路徑 (例如 foo, ::std::mem::replace, transmute::<_, int>, …)
  • meta: 元條目,即被包含在 #[...]及#![...]屬性內(nèi)的東西。
  • tt: 標(biāo)記樹

大多數(shù)情況,一般只會(huì)使用expr和tt, 使用expr是因?yàn)閞ust中幾乎可以被稱為基于表達(dá)式的編程語(yǔ)言,因?yàn)樗谋磉_(dá)式概念非常大,即使是if和while這樣的語(yǔ)句也可以作為一個(gè)表達(dá)式返回值,而tt是一個(gè)萬(wàn)金油,它可以簡(jiǎn)單的被認(rèn)為是其他類型都不匹配的情況下的兜底類型。
下面看一個(gè)tt類型的例子。

macro_rules! add {
    ($a: tt) => {
        {
            println!("{}", stringify!($a));
            1
        }
    };
}

fn main() {
    let sum = add!(1);
    println!("sum: {sum}");
    let sum = add!(,);
    println!("sum: {sum}");
    let sum = add!({});
    println!("sum: {sum}");
    let sum = add!(youerning);
    println!("sum: {sum}");
}

代碼輸出如下:

1
sum: 1
,
sum: 1
{}
sum: 1
youerning
sum: 1

代碼展開后長(zhǎng)這樣:

值得注意的是: 下面的代碼是手動(dòng)的展開,與真實(shí)的編譯代碼還是有點(diǎn)區(qū)別的!!!

fn main() {
    let sum = {
        println!("{}", "1")
        1
    };
    println!("sum: {sum}");
    
    let sum = {
        println!("{}", ",")
        1
    };
    println!("sum: {sum}");
    let sum = {
        println!("{}", "{}")
        1
    };
    println!("sum: {sum}");
}

總的來(lái)說(shuō), tt這個(gè)類型可以接受合法或者不合法的各種標(biāo)識(shí)符。

stringify!是啥?  說(shuō)實(shí)話我也不太懂,我的理解是,你可以將任何東西扔給它,它會(huì)返回一個(gè)字符串字面量給你。

宏展開(expand)

如果我真的能夠手動(dòng)展開自己的代碼,那就肯定會(huì)了,也就不用開文章學(xué)習(xí)了不是,所以如果吃不準(zhǔn)宏展開之后的結(jié)果或者故障排查的時(shí)候可以使用cargo expand命令查看展開后的代碼。
可以通過(guò)以下命令安裝。

cargo install cargo-expand

安裝之后在項(xiàng)目的根目錄執(zhí)行cargo expand即可,上面的例子展開之后如下。

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
fn main() {
    let sum = {
        {
            ::std::io::_print(format_args!("{0}\n", "1"));
        };
        1
    };
    {
        ::std::io::_print(format_args!("sum: {0}\n", sum));
    };
    let sum = {
        {
            ::std::io::_print(format_args!("{0}\n", ","));
        };
        1
    };
    {
        ::std::io::_print(format_args!("sum: {0}\n", sum));
    };
    let sum = {
        {
            ::std::io::_print(format_args!("{0}\n", "{}"));
        };
        1
    };
    {
        ::std::io::_print(format_args!("sum: {0}\n", sum));
    };
    let sum = {
        {
            ::std::io::_print(format_args!("{0}\n", "youerning"));
        };
        1
    };
    {
        ::std::io::_print(format_args!("sum: {0}\n", sum));
    };
}

如果看不太懂可以結(jié)合我手動(dòng)展開的代碼一起看。

標(biāo)記樹撕咬機(jī)(TT muncher)

通過(guò)標(biāo)記樹撕咬機(jī)(TT muncher)我們可以實(shí)現(xiàn)遞歸的聲明宏,不過(guò)在此之前讓我們先解決不定參數(shù)的問(wèn)題,之前解決的方案是根據(jù)要傳的參數(shù)編寫聲明宏的匹配代碼,這樣實(shí)在是太不優(yōu)雅了,讓我們看看怎么一次性搞定。

macro_rules! add {
    ($($a: expr),*) => {
        0$(+$a)*
    };
}

fn main() {
    let sum = add!();
    println!("sum1: {sum}");
    let sum = add!(1);
    println!("sum1: {sum}");
    let sum = add!(1,2);
    println!("sum2: {sum}");
    let sum = add!(1,2,3);
    println!("sum3: {sum}");
}

輸出如下:

sum1: 0
sum1: 1
sum2: 3
sum3: 6

重復(fù)

聲明宏里面有一些難點(diǎn),其中一個(gè)就是重復(fù)的匹配模式, 也就是這個(gè)例子中的$($a: expr),*, 為啥要這樣寫? 因?yàn)檫@是rust的語(yǔ)法, 就像定義一個(gè)新變量必須使用let表達(dá)式一樣,這個(gè)不需要太糾結(jié)。

下面來(lái)看看這種模式的語(yǔ)法定義,重復(fù)的一般形式是$ ( ... ) sep rep

  • $ 是字面標(biāo)記。
  • ( ... ) 代表了將要被重復(fù)匹配的模式,由小括號(hào)包圍。
  • sep是一個(gè)可選的分隔標(biāo)記。常用例子包括,和;。
  • rep是重復(fù)控制標(biāo)記。當(dāng)前有兩種選擇,分別是* (代表接受0或多次重復(fù))以及+ (代表1或多次重復(fù))。目前沒(méi)有辦法指定“0或1”或者任何其它更加具體的重復(fù)計(jì)數(shù)或區(qū)間。

大家可以將($($a: expr),*)改成($($a: expr);*),然后就會(huì)發(fā)現(xiàn)編譯不過(guò)了,因?yàn)榉指舴枰?了

也就是說(shuō), $($a: expr),*匹配到了(), (1), (1,2),(1,2,3),為啥能匹配到()?, 因?yàn)?能匹配0個(gè)或多個(gè),所以零參數(shù)的()也能匹配上,如果你將這個(gè)例子中的*換成+,就會(huì)發(fā)現(xiàn)add!()會(huì)報(bào)錯(cuò),因?yàn)?要求至少一個(gè)參數(shù)。

下面以參數(shù)(1,2,3)的例子再深入一下宏展開時(shí)的操作,當(dāng)傳入(1,2,3)時(shí),因?yàn)楦?($a: expr),*能夠匹配上, 所以(1,2,3)里的冒號(hào),被$($a: expr),*的冒號(hào),給匹配上,而$a代表1 2 3中的每個(gè)元素, 那么怎么在返回的代碼中標(biāo)識(shí)重復(fù)的參數(shù)呢?rust的語(yǔ)法是, 我們需要使用$()*將$a包裹起來(lái),外面的包裝代碼對(duì)應(yīng)參數(shù)匹配時(shí)的重復(fù)次數(shù), 你可以簡(jiǎn)單的將$()*認(rèn)為是必要的語(yǔ)法。

下面看一個(gè)簡(jiǎn)單的例子

macro_rules! print {
    ($($a: expr),*) => {
        println!("{} {}", $($a),*)
    };
}

fn main() {
    print!(1,2);
}

$($a),*會(huì)原封不動(dòng)的將參數(shù)放在它對(duì)應(yīng)的位置,因?yàn)閜rintln!指定了兩個(gè)位置參數(shù),所以使用自定義的print只能傳遞兩個(gè)參數(shù)。

最后看看上面那個(gè)add!宏的例子, add!(1,2,3)展開之后應(yīng)該變成下面這樣。

0+1+2+3

之所以這樣,是因?yàn)槲覀冊(cè)诜祷氐拇a模式中$($a)*在$a前面加了一個(gè)+, 而這個(gè)加號(hào)+因?yàn)楸?()*包裹,所以會(huì)跟著$a重復(fù)一樣的次數(shù),也就變成了+1+2+3。
為啥前面要加個(gè)0?因?yàn)椴患?的話, 就不是合法的表達(dá)式了。

遞歸示例1

雖然add!這個(gè)宏可以使用一個(gè)模式匹配就能完成,但是我們可以使用更加復(fù)雜的方式實(shí)現(xiàn),也就是標(biāo)記樹撕咬機(jī)(TT muncher)。

macro_rules! add {
    ($a: expr) => {
        $a
    };
    ($a: expr, $b: expr) => {
        $a + $b
    };
    ($a: expr, $($other: tt)*) => {
        $a + add!($($other)*)
    };
}

fn main() {
    let sum = add!(1,2,3,4,5);
    println!("sum: {sum}");
}

使用**標(biāo)記樹撕咬機(jī)(TT muncher)**的代碼和之前的代碼結(jié)果沒(méi)有什么區(qū)別,但是展開的過(guò)程中會(huì)有些不同,因?yàn)楹笳呤褂昧诉f歸,它的遞歸調(diào)用類似于add!(1, add!(2, add!(3, add!(3, add!(3, add!(5))))));

這段代碼的前兩個(gè)匹配模式不用過(guò)多介紹,關(guān)鍵在于最后一個(gè)($a: expr, $($other: tt)*), $a 和 ,會(huì)吃掉一個(gè)參數(shù)和一個(gè)逗號(hào),, 而$($other: tt)*會(huì)匹配到后面所有的參數(shù)2,3,4,5。

注意這些參數(shù)包含逗號(hào),, 還有就是我們?cè)谑褂?($other: tt)*這種重復(fù)模式的時(shí)候沒(méi)有指定分隔符, 所以tt既匹配了參數(shù)2 3 4 5也匹配了分割這些數(shù)字的逗號(hào),, 所以在展開的代碼$a + add!($($other)*)會(huì)變成1 + add!(2,3,4,5), 然后就是不斷的遞歸了,直到遇到第一個(gè)匹配模式。

遞歸示例2

你可能在上一個(gè)例子不能感受到**標(biāo)記樹撕咬機(jī)(TT muncher)**的威力,所以我們繼續(xù)看下一個(gè)例子。
我們可以通過(guò)**標(biāo)記樹撕咬機(jī)(TT muncher)**的遞歸調(diào)用來(lái)生成對(duì)嵌套對(duì)象的遞歸調(diào)用,這樣就不需要不斷的判斷Option的值是Some還是None了。

use serde_json::{json, Value};


macro_rules! serde_get {
    ($value: ident, $first: expr) => {
        {
            match ($value).get($first) {
                Some(val) => Some(val),
                None => {
                    None
                }
            }
        }
    };

    ($value: ident, $first: expr, $($others:expr),+) => {
        {
            match ($value).get($first) {
                Some(val) => {
                    serde_get!(val, $($others),+)
                },
                None => {
                    None
                }
            }
        }
    };

    ($value: ident, $first: expr, $($others:tt)* ) => { 
        {
            match ($ident).get($first) {
                Some(val) => {
                    serde_get!(val, $($others)+),
                }
                None => None
            }
        }
    };
    
}


fn main() {
    let object = json!({
        "key11": {"key12": "key13"},
        "key21": {"key22": {"key23": "key24"}}
    });

    if let Some(val) = serde_get!(object, "xx") {
        println!(r#"object["a"]["b"]["c"]={val:?}"#);
    } else {
        println!(r#"object["a"]["b"]["c"]不存在"#);
    }

    if let Some(val) = serde_get!(object, "key1", "key12") {
        println!(r#"object["key11"]["key12"] = {val:}"#);
    }

    if let Some(val) = serde_get!(object, "key21", "key22", "key23") {
        println!(r#"object["key21"]["key21"]["key23"] = {val:}"#);
    }
}

這個(gè)例子寫完,我才發(fā)現(xiàn)serde_json可以直接使用["key21"]["key21"]["key23"]這樣的語(yǔ)法直接判斷!!!, 不過(guò)serde_json的返回結(jié)果都是null, 如果鍵值對(duì)不存在的話。

總結(jié)

我感覺(jué)rust的宏編程還是很有意思的,不過(guò)這東西的確得真正有需求的時(shí)候才會(huì)真的理解,我之前也不是太懂,看了視頻和文章也不是太懂,只是知道它能干啥,但是沒(méi)有一個(gè)真正要解決的問(wèn)題,所以一直不能很好的掌握,直到在使用serde_json時(shí)遇到嵌套的數(shù)據(jù)結(jié)構(gòu)需要寫重復(fù)的判斷代碼時(shí),我才在應(yīng)用的時(shí)候掌握了聲明宏(雖然最后發(fā)現(xiàn)它的實(shí)用價(jià)值可能不是那么大),至于過(guò)程宏,可能等我遇到需要過(guò)程宏的時(shí)候才會(huì)很好的掌握吧,到時(shí)候在寫對(duì)應(yīng)的文章吧。

參考鏈接

https://earthly.dev/blog/rust-macros/
https://doc.rust-lang.org/reference/macros-by-example.html#metavariables
https://www.bookstack.cn/read/DaseinPhaos-tlborm-chinese/mbe-macro-rules.md
https://veykril.github.io/tlborm/
https://github.com/dtolnay/cargo-expandhttps://youerning.top/post/rust/rust-declarative-macros-tutorial/

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

相關(guān)文章

  • Rust 的 into_owned() 方法實(shí)例詳解

    Rust 的 into_owned() 方法實(shí)例詳解

    into_owned是Rust語(yǔ)言中std::borrow::Cow 枚舉的一個(gè)方法,into_owned確保了調(diào)用者獲得數(shù)據(jù)的獨(dú)立所有權(quán),無(wú)論Cow之前是引用還是已經(jīng)擁有數(shù)據(jù),本文給大家介紹Rust 的 into_owned() 方法,感興趣的的朋友跟隨小編一起看看吧
    2024-03-03
  • Rust?實(shí)現(xiàn)?async/await的詳細(xì)代碼

    Rust?實(shí)現(xiàn)?async/await的詳細(xì)代碼

    異步編程在 Rust 中的地位非常高,很多 crate 尤其是多IO操作的都使用了 async/await,這篇文章主要介紹了Rust?如何實(shí)現(xiàn)?async/await,需要的朋友可以參考下
    2022-09-09
  • Rust 文檔注釋功能示例代碼

    Rust 文檔注釋功能示例代碼

    Rust的文檔注釋使用特定的格式,以便通過(guò) rustdoc工具生成 API 文檔,本文給大家介紹Rust 文檔注釋功能,感興趣的朋友跟隨小編一起看看吧
    2024-04-04
  • Rust生命周期常見(jiàn)誤區(qū)(中英對(duì)照)全面指南

    Rust生命周期常見(jiàn)誤區(qū)(中英對(duì)照)全面指南

    這篇文章主要WEIDJAI?介紹了Rust生命周期常見(jiàn)誤區(qū)(中英對(duì)照)的全面指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • 深入理解 Rust 中的模式匹配語(yǔ)法(最新推薦)

    深入理解 Rust 中的模式匹配語(yǔ)法(最新推薦)

    Rust中的模式匹配提供了多種方式來(lái)處理不同的數(shù)據(jù)類型和場(chǎng)景,本文給大家介紹Rust 中的模式匹配語(yǔ)法,感興趣的朋友一起看看吧
    2025-03-03
  • Rust如何使用config配置API

    Rust如何使用config配置API

    這篇文章主要介紹了Rust如何使用config配置API,這里記錄了如何聲明配置類型,讀取配置,通過(guò)環(huán)境變量來(lái)覆蓋配置值等開發(fā)中常見(jiàn)的動(dòng)作,需要的朋友可以參考下
    2023-11-11
  • Rust之Rhai腳本編程的示例

    Rust之Rhai腳本編程的示例

    文章介紹了如何在Rust中使用Rhai庫(kù)執(zhí)行腳本,包括添加依賴、創(chuàng)建引擎、定義腳本、執(zhí)行腳本并處理結(jié)果,示例展示了如何定義一個(gè)簡(jiǎn)單的函數(shù)并調(diào)用它,輸出問(wèn)候信息
    2025-02-02
  • Rust中的Iterator和IntoIterator介紹及應(yīng)用小結(jié)

    Rust中的Iterator和IntoIterator介紹及應(yīng)用小結(jié)

    Iterator即迭代器,它可以用于對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行迭代,被迭代的數(shù)據(jù)結(jié)構(gòu)是可迭代的(iterable),所謂的可迭代就是這個(gè)數(shù)據(jù)結(jié)構(gòu)有返回迭代器的方法,這篇文章主要介紹了Rust中的Iterator和IntoIterator介紹及應(yīng)用,需要的朋友可以參考下
    2023-07-07
  • Rust中的Box<T>之堆上的數(shù)據(jù)與遞歸類型詳解

    Rust中的Box<T>之堆上的數(shù)據(jù)與遞歸類型詳解

    本文介紹了Rust中的Box<T>類型,包括其在堆與棧之間的內(nèi)存分配,性能優(yōu)勢(shì),以及如何利用Box<T>來(lái)實(shí)現(xiàn)遞歸類型和處理大小未知類型,通過(guò)Box<T>,Rust程序員可以更靈活地管理內(nèi)存,避免編譯時(shí)大小不確定的問(wèn)題,并提高代碼的效率和靈活性
    2025-02-02
  • Rust 多線程編程的實(shí)現(xiàn)

    Rust 多線程編程的實(shí)現(xiàn)

    在rust中,多線程編程不算困難,但是也需要留心和別的編程語(yǔ)言中不同的地方,本文主要介紹了Rust 多線程編程的實(shí)現(xiàn),感興趣的可以了解一下
    2023-12-12

最新評(píng)論

武宁县| 松溪县| 南川市| 隆化县| 德清县| 高淳县| 长治市| 光泽县| 敦化市| 昆山市| 七台河市| 舒城县| 将乐县| 阜新市| 广平县| 凤冈县| 德阳市| 周宁县| 盐山县| 茂名市| 穆棱市| 侯马市| 札达县| 长宁县| 徐州市| 剑川县| 通城县| 房产| 湘乡市| 辛集市| 佛学| 博兴县| 阿坝| 延安市| 竹溪县| 沾益县| 三明市| 湛江市| 新竹县| 井冈山市| 镇江市|