天天看點

Rust : 宏、重複、及向量與遞歸執行個體

Rust的宏功能強大,絕對不是花架子以及一堆醜陋的代碼堆而已,它值得你花點時間。Rust宏中,有一個難點就是重複(repetition)功能。這裡想主要闡述一下。

關于宏相關的進階的資料,可以參考《Rust宏小冊 中文版》,具體資料見:https://daseinphaos.github.io/tlborm-chinese/book/README.html。

一、重複的模式

重複的模式需要用括号括起來,外面再加上 $。括号裡面,是标記一個模式的開始和結束,往往是用逗号或分号分隔的。

幾種主要的重複模式,基本上就是“,”“;”,“+”,“*”的組合模式。

一種組合,這種情況更多放在宏的執行動作中,其中“,”、“;”看場景,有時侯也可以不用。

$(...,)*   
$(...;)*
$(...,)+  
$(...;)+
           

另一種組合:往往放在宏的參數()中,表明各輸入參數是以“;”還是“,”隔開。

$(...),*   //參數以“,”隔開,類似: let mut t =vec![1,2,3];
 $(...);*   //參數以“;”隔開,類似: let mut t =vec![1;2;3]; 以下類推.
 $(...),+  
 $(...);+
           

其中,

+  表示一次或多次(至少一次),而  *  表示零次或多次。
 () 括号,也可以用[],{}代替,隻不過最好按慣用的規則,更清晰。
           

二、類似于vec!的例子

//version 1.0
macro_rules! vector {
($($x:expr),*) => {             // 1、為什麼這裡面用“,”号?
    {
       let mut temp_vec = Vec::new();
       $(temp_vec.push($x);)* // 2、還有其它的寫法? 3、為什麼這裡用“;”
       temp_vec
    }
};
}
           

上面2個問題:

(1)、關于問題1,用“,”的目的是什麼?我們知道,

let mut temp =vec![1,2,3]; // 當然也可以 let mut temp =vec!(1,2,3); 也可以用{}。
           

中,1,2,3是用“,”分隔的。如果用“;”那麼,這個分隔号就必須用“;”。那如果我“,”或“;”都可以用呢? 你可以選擇改一下上面的代碼。

//version 2.0
macro_rules! vector {
($($x:expr),*) => {
    {
       let mut temp_vec = Vec::new();
       $(temp_vec.push($x);)*
       temp_vec
    }
};
($($x:expr);*) => {
    {
       let mut temp_vec = Vec::new();
       $(temp_vec.push($x);)*
       temp_vec
    }
};
}
           

需要說明的是,即使如此:

let mut data =vector![1,2;3]; // 這種“,”、“;”混用,仍然是不行的;
           

(2)、關于問題2

除“ * ”可以改成“+”外,

$(temp_vec.push($x);)* //這個模式和前四種不一樣呀
           

還可以:

//version 3.0
macro_rules! vector {
($($x:expr),*) => {
    {
       let mut temp_vec = Vec::new();
       ($(temp_vec.push($x)),*);  // 這裡的“,”不能用“;”替代。
       temp_vec
    }
};
}
           

另外,來一個簡化版的,去掉上面的括号:

//version 4.0
macro_rules! vector {
($($x:expr),*) => {
    {
       let mut temp_vec = Vec::new();
       $(temp_vec.push($x)),* //簡化版的,“*”可以換成“+”
       temp_vec
    }
};
}
           

此外,在version3.0的基礎上,寫一個可以“;”的版本。version3.0的版本不能用“;”的原因,很可能是讓編譯器産生歧義,是以,我們可以改寫一下:

//version 5.0
macro_rules! vector {
($($x:expr),*) => {
    {
       let mut temp_vec = Vec::new();
       $(temp_vec.push($x));*;  // 這裡可以用“;”或“,”,因為結尾加了一個“;”
       temp_vec
    }
};
}
           

( 3 )、“;”不可以換成“,”

(4)、注意,宏的重複語句可以不用“;”結尾

$(temp_vec.push($x);)*;  // 結尾“;”可以省略
      或
      $(temp_vec.push($x)),+;  // 同樣,結尾“;”可以省略
      
           

三、一個遞歸(recursion)的例子

我們再來看下面的一個例子(注:來源rustprimer)。借用這個例子的原因是這個例子有很多值得分析的地方。

macro_rules! find_min {
   ($x:expr) => ($x);
   ($x:expr, $($y:expr),+) => (              // 1、第一個逗号和第二個逗号 
       std::cmp::min($x, find_min!($($y),+)) // 2、find_min!($($y),+))還有其它的寫法?
   )
}
// 下面的說明也來源自rustprimer.
因為模式比對是按分支順序比對的,一旦比對成功就不會再往下進行比對(即使後面也能比對上),是以
模式比對中的遞歸都是在第一個分支裡寫最簡單情況,越往下包含的情況越多。這裡也是一樣,第一個分
支  ($x:expr)  隻比對一個表達式,第二個分支比對兩個或兩個以上表達式,注意加号表示比對一個或多個,然後裡面是用标準庫中的  min  比較兩個數的大小,第一個表達式和剩餘表達式中最小的一個,其中剩餘表達式中最小的一個是遞歸調用  find_min!  宏,與遞歸函數一樣,每次遞歸都是從上往下比對,隻到比對到基本情況。我們來寫寫  find_min!(5u32, 2u32 * 3, 4u32)  宏展開過程
1.  std::cmp::min(5u32, find_min!(2u32 * 3, 4u32))
2.  std::cmp::min(5u32, std::cmp::min(2u32 * 3, find_min!(4u32)))
3.  std::cmp::min(5u32, std::cmp::min(2u32 * 3, 4u32))
分析起來與遞歸函數一樣,也比較簡單。
           

上面的2個問題:

1、問題1

第一個逗号能換成分号麼 ?

當然可以,但是,要改的不是一點了。這相當于需要編譯器接受“;”為分隔号。

接受比如以下的格式:

let mut data =find_min2[1;2;3];
           

具體的改法如下:

macro_rules! find_min2 {
   ($x:expr) => ($x);
   ($x:expr; $($y:expr);*) => (                 //兩個“;”分隔号
       std::cmp::min($x, find_min2!($($y);*))   // 這裡也要改成“;”分隔号,而且不能用“,”
   )
}
           

第二個逗号能去掉麼? 不行,因為你這裡要告訴編譯器,你接受的參數是用什麼來進行分隔的。

2、問題2

當然,上面的例子,直接把“+”換成“*”是沒有問題的;

除此之外,能否換成其它模式? 比如下面把其中的","去掉,形成簡化的形式?

find_min!($($y),+) =>    find_min!($($y)+)
           

我認為:不可以(沒有找到其它的成功的案例)。因為在這裡find_min!()内的參數形式,要有參數”分隔“的形式感。

四、訂制化的println

macro_rules! my_println {
    () => {
        println!("macro, my println!");
    };
    ($val:expr) => {
        println!("Look at this other macro: {}", $val);
    };
    ($($x:expr),*) => {
        $(println!("{}",$x);)* 
    };
}
fn main() {
    my_println!(1, 2);
    thread::sleep_ms(500000);
}
           

輸出是:

1

2