1. 程式人生 > 其它 >Rust中的宣告巨集

Rust中的宣告巨集

前言

宣告式巨集(Declarative macros)使得你能夠寫出類似 match 表示式的東西,來操作你所提供的 Rust 程式碼。它使用你提供的程式碼來生成用於替換巨集呼叫的程式碼。

語法

巨集通過使用macro_rules!來宣告,最為常見的一個宣告式巨集就是println!

macro_rules! 名稱 {
    // 規則
    (元變數:型別) => {轉錄器,用來擴充套件程式碼};
}
  • 規則(rule):至少有一個,($matcher) => {$expansion}為一個規則。
  • 匹配器(matcher):可以是()、{}或[],用來匹配輸入的引數。以上的(元變數:型別)
    即為一個匹配器。
  • 元變數(metavariables):在匹配其中匹配元變數,可在轉錄器中使用。
  • 轉錄器(transcriber): 用來在巨集匹配成功後, 進行程式碼替換。

在一個巨集中,可以有多個分支,巨集根據不同的引數展開到不同的程式碼。每個分支可以接收多個引數,這些引數使用$符號開頭,然後跟著一個 token 型別:

  • item :一個項(item),如一個函式,結構體,模組等。

  • block :一個塊 (block),即一個語句塊或一個表示式,由花括號所包圍。

  • stmt : 一個語句(statement)。

  • pat :一個模式(pattern),如match表示式的(pattern)。

  • expr : 一個表示式(expression)。

  • ty :一個型別(type),如i32,u32,String,Option等。

  • ident: 一個識別符號(indentfier)。

  • path : 一個路徑(path),如foo::std::mem::replacetransmute::<_, int>,...。

  • meta : 一個元資料項;例如#[...]#![...]屬性,meta為[]內的值。

  • tt:一個詞法樹(token tree)。

  • vis:一個可能為空的Visibility限定詞,如pub

  • lifetime:一個生命週期(例如 'foo

    , 'static, ...)

  • literal:一個字面量(e.g. "Hello World!", 3.14, ...)

    詳見

使用宣告式巨集

建立

建立一個名叫add的過程巨集,有三個分支:

  • 第一個分支沒有引數,在轉錄器中將程式碼替換為0。
  • 第二個分支接收一個引數,並在轉錄器中將程式碼替換為它本身。
  • 第三個分支接收兩個引數,並在轉錄器中將程式碼替換為兩個數相加。
macro_rules! add {
    // 匹配器1
    () => {{
        0
    }};
    // 匹配器2
    ($a:expr) => {{
        $a
    }};
    // 匹配器3
    ($a:expr,$b:expr) => {{
        $a + $b
    }};
}

fn main() {
    println!("{:?}", add!()); // 0
    println!("{:?}", add!(1)); // 1
    println!("{:?}", add!(1, 2)); // 3
}

重複匹配

上面的例子每個匹配器只能匹配一種引數情況,如果我們要1+...+n,我們不能寫n個匹配器去匹配。這時候就要用到重複匹配。可以接收可變數量的引數,類似於正則表示式:

  • *:用於匹配大於等於0個引數。
  • +:用於匹配大於1引數。
  • ?:用於匹配0或1個引數。

我們可以簡化上面的程式碼為:

macro_rules! add {

    // ($a:expr):要匹配的模式
    // ,:分隔符,重複符號位?時不需要
    // *:重複符號
    ($($a:expr),*)=>{{
        // 沒引數時返回,必須有,否則報錯
        0
        // 重複程式碼塊
        $(+$a)*
    }};
}

fn main() {
    println!("{:?}", add!()); // 0
    println!("{:?}", add!(1)); // 1
    println!("{:?}", add!(1, 2)); // 3
}

使用宣告式巨集進行高階解析

我們以Student結構體為例,建立一個StudentCopy的結構體並將Student中的屬性設定為pub。結構體如下:

		#[derive(Debug,Clone)]
        struct Student{
            name:String,
            age:i32,
            gender:u8,
        }

解析結構體

macro_rules! make_public{
    (
     // struct上定義的元資料
     $(#[$meta:meta])*
     // struct:可見範圍 struct 名稱
     $vis:vis struct $struct_name:ident {
        $(
            // 欄位上定義的元資料
            $(#[$field_meta:meta])*
            // 欄位:可見範圍 名稱 :型別
            $field_vis:vis $field_name:ident : $field_type:ty
        ),*$(,)+
    }
    ) => {
            $(#[$meta])*
            pub struct $struct_name{
                $(
                    $(#[$field_meta:meta])*
                    pub $field_name : $field_type,
                )*
            }
    }
}

fn main() {
    make_public! {
        #[derive(Debug,Clone)]
        struct Student{
            name:String,
            age:i32,
            gender:u8,
        }
    };
    let x = Student {
        name: String::from("張三"),
        age: 18,
        gender: 1,
    };
    println!("{:?}", x);
}

檢視輸出

Student { name: "張三", age: 18, gender: 1 }

檢視巨集展開後代碼

cargo expand檢視展開後的程式碼,可以看看到結構體Student相關屬性前都加了pub修飾符。

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
fn main() {
    pub struct Student {
        pub name: String,
        pub age: i32,
        pub gender: u8,
    }
    #[automatically_derived]
    impl ::core::fmt::Debug for Student {
        fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
            ::core::fmt::Formatter::debug_struct_field3_finish(
                f,
                "Student",
                "name",
                &&self.name,
                "age",
                &&self.age,
                "gender",
                &&self.gender,
            )
        }
    }
    #[automatically_derived]
    impl ::core::clone::Clone for Student {
        #[inline]
        fn clone(&self) -> Student {
            Student {
                name: ::core::clone::Clone::clone(&self.name),
                age: ::core::clone::Clone::clone(&self.age),
                gender: ::core::clone::Clone::clone(&self.gender),
            }
        }
    }
    let x = Student {
        name: String::from("張三"),
        age: 18,
        gender: 1,
    };
    {
        ::std::io::_print(
            ::core::fmt::Arguments::new_v1(
                &["", "\n"],
                &[::core::fmt::ArgumentV1::new_debug(&x)],
            ),
        );
    };
}

總結

本文列出宣告巨集的基本使用,希望對大家有幫助。

參考

Macros in Rust: A tutorial with examples: https://blog.logrocket.com/macros-in-rust-a-tutorial-with-examples/
A Beginner’s Guide to Rust Macros: https://medium.com/@phoomparin/a-beginners-guide-to-rust-macros-5c75594498f1

留個懸念

你覺得下面這段程式碼會輸出幾?

macro_rules! create_var {
    () => {
        let a = 1;
    };
}
fn main() {
    let a = 2;
    create_var!();
    println!("{}", a);
}