動態

詳情 返回 返回

rust學習二十.14、RUST宏 - 動態 詳情

毫無疑問,宏是rust中極其重要的存在,只要我們看看一些標準庫的代碼。本文闡述rust宏相關的若干問題

 一、宏的定義和作用

先介紹下宏的起源和歷史,以便讀者更能體會。

以下內容來自文心一言生成:

詞源與早期含義

  1. 希臘-拉丁詞根
    • "macro"源於希臘語前綴μακρο-makro-),意為“大”或“長”。拉丁語繼承為macro-(如macro-instruction),保留“大”的核心含義。
    • 在英語中,"macro"作為形容詞表示“巨大的”(如macroeconomic),作為名詞則特指計算機領域的“宏指令”。
  2. 早期通用含義
    • 在非技術領域,"macro"常用於描述大規模事物(如macro-scale planning),與“微觀”(micro)形成尺度對比。

計算機領域的起源與演變

  1. 20世紀50年代:彙編語言的宏指令
    • 背景:早期計算機編程依賴彙編語言,重複性任務(如寄存器操作)導致代碼冗長。
    • 突破:IBM 705計算機(為陶氏化學公司開發)首次引入宏指令macro-instruction),允許用簡短符號代替複雜指令序列。
  2. 1960年代:LISP語言的元編程宏
    • 關鍵人物:Timothy Hart在1963年提出在LISP 1.5中添加宏,通過宏編譯器將高階語法轉換為底層代碼。
    • 意義:宏成為編程語言擴展機制,支持用户自定義語法(如定義領域專用語言DSL)。
  3. 1990年代:VBA與辦公自動化
    • 轉折點:微軟在Office套件中集成VBA(Visual Basic for Applications),使宏進入大眾視野。
    • 應用爆發:用户可通過錄制宏自動化Excel公式計算、Word文檔排版等操作,顯著提升辦公效率。

基本沒有什麼毛病!

概括下:

1.macro(宏)-起源於希臘,後拉丁借用,最後是英語用上了。

2.macro的核心意思就是:巨大,宏觀(大規模現象之意)

3.在計算機領域,大體保留了對macro的含義,例如宏病毒,宏指令

具體到計算機,可以這樣理解:宏或者宏指令就是表達了一大段代碼的對象(命令/指令等),表達了有許多代碼的意思。

這是一個習慣問題。只要願意,我們也可以稱為:多指令集合,多代碼集合。

 

所以,總結起來,rust宏定義:用於表示一段代碼或者條件語句,其構成可能是一段代碼,或則可能也沒有任何代碼。

或者説宏的核心作用就是兩個

1.條件編譯

2.表示一段代碼,有助於簡化編程

從這裏可以看出rust宏和C++宏的作用極其相似!

二、宏實現原理

從其自用可以看出,其實現原理相當簡單:

1.如果是條件編譯指令,那麼編譯器發現後當做條件執行

2.如果表示一段代碼,則rust編譯器會把指令替換為一段代碼(大體是提單,但具體上應該還是有一些額外的內容)

宏都是在編譯的時候起到作用!

三、宏和函數區別

書籍作者為什麼要提到這個? 開始的時候我不太明白,直到我看了宏的代碼。

宏的代碼看起來有點像函數。

這裏先總結下rust中宏和函數的區別:

1.宏可不僅僅是一段代碼,還可以作為編譯條件

2.宏可以接收不同個數參數(或者説不定個數參數),而函數不能

3.宏是在編譯環節起作用(編譯器會根據宏類型來決定作為編譯條件還是實現一段代碼),而函數是在運行時起到作用

 

四、標準宏

4.1、常見標準宏

表_常見標準宏

宏名稱 功能描述 所屬模塊/類型
assert! 斷言表達式為 true,否則觸發 panic(常用於單元測試) 標準庫(測試相關)
assert_eq! 斷言兩個值相等,否則觸發 panic 標準庫(測試相關)
assert_ne! 斷言兩個值不相等,否則觸發 panic 標準庫(測試相關)
compile_error! 在編譯期生成錯誤(用於自定義編譯時檢查) 標準庫(元編程)
concat! 連接多個字面量,返回 &'static str 標準庫(字符串操作)
column! 返回當前源代碼的列號 標準庫(調試信息)
dbg! 輸出表達式的值和類型(調試工具) 標準庫(調試相關)
eprint! 輸出到標準錯誤流(不換行) 標準庫(I/O 操作)
eprintln! 輸出到標準錯誤流(自動換行) 標準庫(I/O 操作)
env! 獲取編譯期環境變量(若變量不存在則報錯) 標準庫(環境變量)
file! 返回當前源代碼的文件名 標準庫(調試信息)
format! 格式化字符串並返回 String 標準庫(字符串操作)
include_bytes! 將文件讀取為字節數組(&'static [u8] 標準庫(文件操作)
include_str! 將文件讀取為字符串(&'static str 標準庫(文件操作)
line! 返回當前源代碼的行號 標準庫(調試信息)
macro_rules! 定義聲明式宏(如 vec! 標準庫(宏定義)
module_path! 返回當前模塊的路徑 標準庫(調試信息)
option_env! 安全獲取編譯期環境變量(返回 Option<&'static str> 標準庫(環境變量)
print! 輸出到標準輸出流(不換行) 標準庫(I/O 操作)
println! 輸出到標準輸出流(自動換行) 標準庫(I/O 操作)
stringify! 將類型或標記轉換為字符串字面量 標準庫(類型操作)
vec! 創建並初始化向量(如 vec![1, 2, 3] 標準庫(集合操作)

以上這些宏是截止2025/04/11,這個是的rust主版本是2021

 

4.2、常見編譯宏

表_rust常見編譯宏

宏名稱 功能描述 所屬模塊/類型
cfg! 在編譯時評估配置標誌的布爾組合(如 if cfg!(target_os = "linux") 標準庫(條件編譯)
debug_assertions 檢查是否啓用了調試斷言(未啓用優化時成立) 標準庫(調試配置)
target_arch 匹配目標平台的CPU架構(如 x86_64arm 標準庫(平台配置)
target_env 匹配目標平台的運行庫環境(如 muslmsvc 標準庫(平台配置)
target_os 匹配目標操作系統(如 linuxwindows 標準庫(平台配置)
target_pointer_width 匹配目標平台的指針寬度(如 3264 標準庫(平台配置)
target_vendor 匹配目標平台的生產商(如 applepc 標準庫(平台配置)
test 標記測試函數(僅在測試模式下編譯) 標準庫(測試配置)

     

表_rust條件編譯控制宏等

名稱 功能描述 示例 大類
compile_note! 輸出編譯提示信息(不中斷編譯) compile_note!("此功能需要Rust 1.60+版本"); 編譯診斷宏
compile_warning! 輸出編譯警告(不中斷編譯) compile_warning!("使用舊版API可能影響性能"); 編譯診斷宏
compile_error! 立即終止編譯並報錯(展開宏後停止) compile_error!("不支持Windows XP系統"); 編譯診斷宏
compile_fatal! 立即終止編譯(最高優先級錯誤) compile_fatal!("致命錯誤:缺少必要配置文件"); 編譯診斷宏
allow 忽略特定警告 #![allow(dead_code)] Lint控制屬性
warn 將特定警告提升為錯誤(編譯失敗) #![warn(missing_docs)] Lint控制屬性
deny 將特定警告提升為錯誤(編譯失敗) #![deny(unused_variables)] Lint控制屬性
forbid 禁止特定代碼模式(最高優先級,編譯失敗) #![forbid(unsafe_code)] Lint控制屬性
deprecated 標記函數/結構體為已棄用(生成警告) // 示例:在函數上添加#[deprecated]屬性 其他相關宏/屬性
must_use 強制檢查返回值是否被使用(如Result類型) // 示例:在結構體上添加#[must_use]屬性 其他相關宏/屬性
cfg/cfg_attr 根據編譯條件啓用不同屬性 // 示例:#[cfg(target_os = "windows")] #[allow(unused_variables)] 其他相關宏/屬性

 

五、自定義宏的編寫

光有標準宏也不夠,rust還允許工程師自定義宏,就和C++一樣。

在rust中可以通過以下兩種方式創建自定義宏

1.使用macro_rules!的申明宏

2.過程宏

  • 自定義 #[derive] 宏,用於在結構體和枚舉上通過添加 derive 屬性生成代碼
  • 類屬性宏,定義可用於任意項的自定義屬性
  • 類函數宏,看起來像函數,但操作的是作為其參數傳遞的 token

5.1、申明宏定義

這是比較常見的定義方式,關鍵在於知道怎麼定義,或者説如何看懂宏定義。

雖然在實際編程中宏的定義不是很多,但是要知道如何看,要在需要定義的時候能夠定義。

我們都知道宏太多的後遺症,C++就是這個毛病,C++也在努力知錯就改!

 

閒話少説,以書本的例子進行解釋:

#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

 

逐個解釋下:

1.#[macro_export]-告訴編譯器當前的宏可以在其它單元包中使用

2. macro_rules! 申明宏的固定開頭

3.vec宏名稱,注意不用帶!

4.宏名vec後的括號{}及其內容是宏主體,包括一個模式匹配表達式(大體是一個正則表達式)和一個生成段(利用匹配表達式中捕獲的變量)

其中2,3部分是固定。

 

上例中的模式匹配表達式 

$( $x:expr ),*
表示這樣的意思:

a.第一個$表示這是一個重複的匹配分組,和正則表達式匹配分組一個概念

b.$x:expr- $x表示捕獲的變量,expr用於修飾$x表示這是一個表達式

c.最後的逗號表示重複分組的分隔符

d.最後的*表示這個重複分組可以0次或者多次,和一般的正則表達式的*一個意思。

需要注意的是:這個模式匹配表達式並不是標準的正則表達式語法。

它既不是基於NFA(非確定性有限自動機)的實現(例如perl,python)     ,也不是基於DFA(確定性有限自動機)的實現(例如java,python),而是rust自己改造過的。

為了便於記憶,也可以成為rust宏模式匹配語法,或者rust宏模式匹配正則語法。

 

注意:一個宏定義中,可以有多個模式匹配表達式,以及每個匹配表達式對應的生成段。 上面這個官方的例子這是給出了最簡單的示例-只是匹配了一種情況。

本文的重點不在於説明如何編寫各種各樣宏的具體細節,重點在於告知宏的主要方面。

如果要知道如何編制宏,可以參考官方給出的文檔  - https://veykril.github.io/tlborm/

 

5.2、過程宏定義

過程宏是什麼意思?

原文是procedural ,中文的意思可以是:程序性的,程序上的。procedural是procedure的形容詞。

在許多語言中,procedure和function基本是一個意思。所以這裏的過程宏意思就是和函數/方法有關的宏?

以下是原書文字:

過程宏接收 Rust 代碼作為輸入,在這些代碼上進行操作,然後產生另一些代碼作為輸出,而非像聲明式宏那樣匹配對應模式然後以另一部分代碼替換當前代碼

創建過程宏時,其定義必須駐留在它們自己的具有特殊 crate 類型的 crate 中。這麼做出於複雜的技術原因,將來我們希望能夠消除這些限制

以下簡要介紹三種過程中的定義,更多的直接看後面例子。

注:實際的定義可能更加複雜多樣,以下內容僅供參考!

更多內容參考: https://veykril.github.io/tlborm/

或者直接閲讀rust的源碼:https://github.com/rust-lang/rust

5.2.1、繼承宏derive定義

繼承宏主要用於實現某個特質。定義特質的繼承宏的主要目標是為了少寫代碼。

如果不習慣使用繼承宏也無所謂。

當然定義一個繼承宏,除了可以少寫代碼,也可以通過這個方式為相關的對象快速地添加其它的功能。

常見的如:DebugClonePartialEq

它的定義形如:

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream

5.2.2、類屬性宏定義

類屬性宏,就外在表現而言,在某種程度上和繼承宏差不多。類屬性宏可以用於函數/方法、struct,枚舉(enum)、聯合體(union)、類型別名(type alias)以及模塊(mod)等所有"項"(Item)類型。

常見的類屬性宏有cfg、test

類屬性宏通常傳遞兩個參數,例如:

#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream
第一個參數是宏後跟上的內容,第二個參數則是具體的"項".
在宏的主題中,需要判斷item的類型,以便做不同的操作.
 

5.2.3、類函數宏定義

類函數宏如起其名,在使用的時候,它表現得和一個函數一樣,例如常見的vec!println!format!

它的定義比較簡單,形如:

#[proc_macro]
pub fn array(input: TokenStream) -> TokenStream

六、示例

內容有點多。

6.1、申明宏示例


/**
 * 1.申明式宏例子
 * 申明式宏以macro_rules! 關鍵字定義,用於在編譯時進行代碼的替換和生成。
 * 後面跟上宏名稱。內容可以包含多個模式匹配表達式,和表達式相對應的替換代碼(或者稱為生成段)
 * 不同的匹配表達式之間使用分號進行分割.
 * 而模式匹配表達式式一種類似於正則表達式的rust定義規則表達式
 */
macro_rules! example {
    // 1. 簡單固定匹配,不需要 $
    () => {
        println!("Empty match!");
    };

    // 2. 固定字面量匹配,不需要 $
    (hello) => {
        println!("Hello match!");
    };

    // 3. 使用元變量的匹配,需要 $
    ($x:expr) => {
        println!("Expression: {}", $x);
    };

    // 4. 混合使用
    (print $x:expr) => {
        println!("Printing: {}", $x);
    };

    // 5. 重複模式,需要 $
    (sum $($x:expr),*) => {
        {
            let mut total = 0;
            $(
                total += $x;
            )*
            total
        }
    };
}



fn main() {
    example!();           // 匹配第1個規則
    example!(hello);      // 匹配第2個規則
    example!(42);         // 匹配第3個規則
    example!(print 42);   // 匹配第4個規則
    let sum = example!(sum 1, 2, 3);  // 匹配第5個規則
    println!("Sum: {}", sum);
}

 

可以看出,申明宏很好用,用起來類似函數宏。

輸出:

 

6.2、過程宏示例

包括三個宏:繼承宏、類屬性宏、類函數宏

為了方便,它們寫在一個rust空間中,結構圖如下:

wsmain是二進制單元主包,attr_macro,func_macro,hello_macro_derive分別表示類屬性宏、類函數宏、繼承宏。

這三個宏都是庫單元。

各個Cargo.toml的文件

//1.wsexample的空間描述文件
[workspace]
resolver = "2"
members = [ "hello_macro_derive",
    "student",
    "teacher", 
    "hello_macro_derive",
    "wsmain", "attr_macro", "func_macro",
]

[workspace.package]
version = "0.1.0"
edition = "2021"
[workspace.dependencies]
rand = "0.9.0-beta.1" 

//2.wsmain工程描述文件
[package]
name = "wsmain"
version.workspace=true
edition.workspace=true

[dependencies]
student={path="../student"}
teacher={path="../teacher"}
hello_macro_derive={path="../hello_macro_derive"}
attr_macro={path="../attr_macro"}
func_macro={path="../func_macro"}

//3.attr_macro描述文件
[package]
name = "attr_macro"
version.workspace = true
edition.workspace = true

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full"] }

//4.func_macro描述文件
[package]
name = "func_macro"
version.workspace = true
edition.workspace = true
[lib]
proc-macro = true

[dependencies]
syn = { version = "2.0", features = ["full"] }
quote = "1.0"

//5.hello_macro_derive描述文件
[package]
name = "hello_macro_derive"
version.workspace = true
edition.workspace = true
[lib]
proc-macro = true
[dependencies]
syn = "2.0"
quote = "1.0"

 

attr_macro庫文件

//引入TokenStream, TokenTree, quote, syn等宏和庫
//其中TokenStream的作用是生成和解析Token流,用於宏的定義和處理。
use proc_macro::{TokenStream, TokenTree};
use quote::quote;
//引入parse_macro_input, ItemFn等宏和庫
//其中parse_macro_input用於解析宏輸入,fnItemFn表示一個函數項,LitStr表示字符串字面量。
use syn::{parse_macro_input, ItemFn};

//基於這個類屬性宏,那麼函數可以這樣引用 #[route(GET,"/")]
#[proc_macro_attribute]
pub fn route(attr: TokenStream, fn_item: TokenStream) -> TokenStream {
    // 解析屬性參數
    let mut method = String::new();
    let mut path = String::new();
    
    for token in attr.clone() {
        match token {
            TokenTree::Literal(lit) => {
                // 假設第二個參數是路徑
                path = lit.to_string().trim_matches('"').to_string();
            }
            TokenTree::Ident(ident) => {
                // 假設第一個參數是HTTP方法
                method = ident.to_string();
            }
            _ => {}
        }
    }

    // 解析原始函數
    let input_fn = parse_macro_input!(fn_item as ItemFn);
    let fn_name = &input_fn.sig.ident;
    let fn_block = &input_fn.block;
    let fn_vis = &input_fn.vis;
    let fn_sig = &input_fn.sig;

    // 生成新的代碼
    let expanded = quote! {
        #fn_vis #fn_sig {
            println!(
                "Route registered - Method: {}, Path: {}, Function: {}",
                #method, #path, stringify!(#fn_name)
            );
            #fn_block
        }
    };

    TokenStream::from(expanded)
}

 

這個宏實現route(GET,"/")這樣的註解。

注意,例子中寫死了是用於函數上,如果用於其它項目,可能會報錯。我們也可以繼續修改,以讓它適用於更多的rust對象:結構、枚舉、別名、模塊等。

 

func_macro庫文件

// 引入必要的過程宏庫
use proc_macro::TokenStream;
use quote::quote;
use proc_macro::TokenTree;

// 定義函數宏
#[proc_macro]
pub fn array(input: TokenStream) -> TokenStream {
    // 打印整個 input 的內容
    //println!("Macro input (raw): {:?}", input);

    // 打印 input 中的每個 token
    println!("工程師在代碼中輸入的內容如下:");
    for (index, token) in input.clone().into_iter().enumerate() {
        //如果token是Literal,那麼打印token.symbol
        //如果token是Punct,那麼打印token.ch
        match token {
            TokenTree::Literal(lit) => println!("Token {}  - Literal: {}",index, lit.to_string()),
            TokenTree::Punct(punct) => println!("Token {}  - Punctuation: '{}'",index, punct.as_char()),
            _ => (),
        }
    }

    // 將 input 轉換為迭代器
    let mut iter = input.into_iter();
    
    // 第一個參數:待分割的字符串
    let content = loop {
        match iter.next() {
            Some(TokenTree::Literal(lit)) => {
                // 找到字面量後返回
                let content_str = lit.to_string().trim_matches('"').to_string();
                println!("Parsed content: {}", content_str);
                break content_str;
            },
            Some(TokenTree::Punct(_)) => {
                // 遇到標點符號就繼續循環
                continue;
            },
            None => panic!("未找到字符串參數"),
            _ => panic!("第一個參數必須是字符串"),
        }
    };

    // 第二個參數:分隔符
    let delimiter = loop {
        match iter.next() {
            Some(TokenTree::Literal(lit)) => {
                // 找到字面量後返回
                let delimiter_str = lit.to_string().trim_matches('"').to_string();
                println!("Parsed delimiter: {}", delimiter_str);
                break delimiter_str;
            },
            Some(TokenTree::Punct(_)) => {
                // 遇到標點符號就繼續循環
                continue;
            },
            None => panic!("未找到分隔符參數"),
            _ => panic!("第二個參數必須是分隔符字符串"),
        }
    };

    // 分割字符串
    let elements: Vec<String> = content
        .split(&delimiter)
        .map(|s| s.trim().to_string())
        .collect();

    // 打印分割後的元素
    println!("Parsed elements: {:?}", elements);

    // 使用 quote! 宏生成代碼
    let expanded = quote! {
        [#(#elements),*]
    };

    // 轉換為 TokenStream 並返回
    TokenStream::from(expanded)
}

 

這個宏是為了實現這樣的功能:把一個字符串切割為一個數組返回,分隔符可以自行定義。

例如 array!("bood,dsd,中國,美國,俄羅斯",",")。

注意,代碼中執行了一些標準rust功能,這些輸出只是在編譯的時候產生,請仔細看最後的輸出。

 

hello_macro_derive庫文件

/**
 * 演示一個過程宏的實現
 * 其中關聯文檔
 * 1. syn  的 DeriveInput 結構  --  https://docs.rs/syn/1.0.109/syn/struct.DeriveInput.html
 * 2. quote crate 的quote! 宏   -- https://docs.rs/quote/latest/quote/
 */

use proc_macro::TokenStream;
use quote::quote;
/**
 * 這是一個過程宏的典型實現,按照書本的闡述,大部分的過程宏的實現結構基本同此
 * 1. 解析輸入的代碼
 * 2. 基於解析的數據結構構建 trait 實現
 * 下面的sysn::parse(input).unwrap() 就是第一步,它產生的結構大體如下:
 * DeriveInput {
            // --snip--

            ident: Ident {
                ident: "Pancakes",
                span: #0 bytes(95..103)
            },
            data: Struct(
                DataStruct {
                    struct_token: Struct,
                    fields: Unit,
                    semi_token: Some(
                        Semi
                    )
                }
            )
        }
 */
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    //1.使用syn crate 將字符串中的 Rust 代碼解析成為一個可以操作的數據結構
    let ast = syn::parse(input).unwrap();

    //2.步驟2- Build the trait implementation
    impl_hello_macro(&ast)
}

fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    // 使用quote!宏構建最終的trait實現
    let gen = quote! {
        impl Hello for #name {
            fn hello_macro() {
                println!("Hello, Macro! My name is {}!", stringify!(#name));
            }

            fn showme(&self)->String{
                self.name.clone()+"[年齡:"+self.age.to_string().as_str()+"]"
            }
            fn get_name(&self)->String{
                self.name.clone()
            }
        }
    };
    gen.into()
}

 

這宏就是為了給特質加功能,使得特定的對象具有特質的功能。

注意:代碼中寫死了對象必須具有name屬性,這基本就意味着它只能用於結構體。

 

wsmain主體(main.rs)

use student::*;
use teacher::*;
use hello_macro_derive::HelloMacro;
use attr_macro::route;
use func_macro::array;

pub trait Hello{
    fn hello_macro();
    fn showme(&self)->String;
    fn get_name(&self)->String;
}

#[derive(Debug, HelloMacro)]
struct Home{
    name: String,
    age: u32,
}

fn main() {
    //1.測試其它單元包的使用情況
    test_other_crate();
    //2.測試盒子指針
    let _me = Box::new(String::from("中國"));

    let lu = Teacherinfo {
        name: String::from("lu"),
        age: 46,
        gender: String::from("男"),
        position: String::from("教研組長"),
    };
    let my_lu = MyTeacherinfo(lu);
    my_lu.print();

    //3.測試用户自定義的各種宏    
    println!("\n\n測試用户自定義的derive宏-----------------------");
    // 宏helloMacro具有3個方法,分別是showme, get_name和hello_macro
    test_udf_derive_macro();
    println!("\n測試用户自定義的類屬性宏-----------------------");
    test_attr_macro();
    println!("\n測試用户自定義的類函數宏-----------------------");
    test_func_macro();
}

struct MyTeacherinfo(Teacherinfo);
impl Print for MyTeacherinfo {
    fn print(&self) {
        println!(
            "教師基本信息-姓名:{},年齡:{},性別:{},職位:{}",
            self.0.name, self.0.age, self.0.gender, self.0.position
        );
    }
}

/**
 * 測試用户自定義的derive宏
 */
fn test_udf_derive_macro(){
    let home = Home{name: String::from("home"), age: 18};
    let me=home.showme();
    println!("{}", me);
    println!("{}", home.get_name());
    Home::hello_macro();
}
#[route(GET,"/")]
fn test_attr_macro() {
}

fn test_func_macro() {
    let arr = array!("1,2,3,4,5",",");
    println!("{:?}",arr);
}   


/**
 * 測試對其它單元包的使用
 */
fn test_other_crate(){
    let lml = Studentinfo {
        name: String::from("lml"),
        age: 18,
        gender: String::from("女"),
        no: String::from("12101"),
    };
    print_student(&lml);
    lml.learn();
    lml.sleep();

    let lu = Teacherinfo {
        name: String::from("lu"),
        age: 46,
        gender: String::from("男"),
        position: String::from("教研組長"),
    };
    lu.teach_student(&lml);
    print_teacher(&lu);
}

 

這個文件中主要是調用各種宏,也有其它一些內容.

輸出結果:

可以看到在編譯信息中有輸出,這是attr_macro的編譯輸出,用於顯示工程師錄入的宏參數。

七、宏的調試技巧

和大部分的語言一言,宏的一個大毛病就是難於調試。

所以,要調試都是一些相對間接的方式,當然不排除以後工具或者方法有了改變,可以很方便。

  1. cargo expand 獲取展開代碼  
  2. 通過 IDE 查看宏展開預覽  -- 僅限於預覽,目前rustover有這個功能
  3. 在過程宏中添加日誌輸出  -- 例如前面的attr_macro就是這樣的
  4. 編寫針對宏的單元測試
  5. 使用 trace_macros!(true) 進行細粒度跟蹤

expand的方式

cargo install cargo-expand
cargo expand --example your_example

 

使用trace_macros

// 在代碼中添加跟蹤標記
#[cfg(debug_assertions)]
macro_rules! trace_macros {
    ($($tt:tt)*) => {
        println!("Macro expanded: {}!", stringify!($($tt)*));
    };
}
// 或使用編譯器內置跟蹤
#[cfg(debug_assertions)]
#[macro_export]
macro_rules! dbg_macro {
    ($($tt:tt)*) => {
        trace_macros!(true);
        $($tt)*
    };
}

 

 

之後build的時候記得添加標記,windows使用set RUSTFLAGS="-Z unstable-options --pretty=expanded"

八、小結

1.總體上rust宏是一個好東西,前提是謹慎使用,不要濫用,因為它不好調試,不容易優化

2.rust有兩大類宏:申明宏和過程宏。過程宏包括繼承宏、類屬性宏、類函數宏

 

Add a new 評論

Some HTML is okay.