1. 程式人生 > 實用技巧 >Rust 學習之運算子過載

Rust 學習之運算子過載

Rust 學習之運算子過載

最近一直在微信讀書上閱讀《深入淺出 Rust》,因為一直在地鐵上閱讀,導致沒辦法在閱讀到的知識點立即驗證和實踐,從而閱讀效果不佳。接著此次有時間,記錄一下其中的運算子過載。

關於運算子過載,在《Rust 程式語言》中沒有找到相關章節,但在《Rust 基礎》(英文名是 RustPrimer)中找到了相關章節

所謂運算子,百度的定義如下:

運算子過載,就是對已有的運算子重新進行定義,賦予其另一種功能,以適應不同的資料型別。

通俗的講,就是自定義一些運算子的功能,使程式碼看起來支援一些特殊資料型別的運算。

此外,《通過例子學 Rust》的描述感覺更易懂:

在 Rust 中,很多運算子可以通過 trait 來過載。也就是說,這些運算子可以根據它們的 輸入引數來完成不同的任務。這之所以可行,是因為運算子就是方法呼叫的語法糖。例 如,a + b 中的 + 運算子會呼叫 add 方法(也就是 a.add(b))。這個 add 方 法是 Add trait 的一部分。因此,+ 運算子可以被任何 Add trait 的實現者使用。

首先我們通過 Rust 標準庫文件的例子入門:

use std::ops::{Add, Sub, Mul, Div};

#[derive(Debug, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {x: self.x + other.x, y: self.y + other.y}
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_point_add() {
        assert_eq!(Point {x: 3, y: 3} + Point {x: 4, y: 4}, Point {x: 7, y: 7});
    }
}

上面的程式碼中,聲明瞭一個新的型別 Point,隨後,為這個型別實現了 Add trait,這樣,在一個表示式中,對 Point 型別的資料進行了 + 運算時,就會呼叫 trait 中的 add 方法,實現 Point 型別的資料的相加。

模擬實現 PHP 中陣列的 +

因為最熟悉的語言是 PHP,對 PHP 中陣列的 + 運算尤為深刻。所以打算對 Rust 陣列也實現類似的功能。

PHP 中兩個陣列相加,有點類似於 merge,但跟 merge 又有所區別:如果第一個陣列中的某個元素下標已存在,則會忽略第二個陣列中相同下標的元素;如果下標 a 在第 1 個數組中不存在,則將第 2 個數組中該下標對應的元素 merge 到新陣列中。比如:

$a1  = [1,2,3];
$a2 = [3,4,5,6,7];
var_dump($a1 + $a2);

$a1$a2 相加後,最終的結果是:[1, 2, 3, 6, 7]。因為 $a2345 對應的下標是 012,而這幾個下標在 $a1 中是已存在的。因此忽略。只將元素 67 merge 進新陣列。

我們再看下 Rust 中陣列相加預設是什麼行為:

#[test]
fn test_vec_plus() {
    let a1: Vec<u32> = vec![1,2,3];
    let a2: Vec<u32> = vec![1,2,4,5,6];
    let a3 = a1 + a2;
    println!("{}-{}", a1, a3)
    assert!(false);
}

執行測試用例 cargo t -- notes::op_rhs::tests

error[E0369]: cannot add `std::vec::Vec<u32>` to `std::vec::Vec<u32>`
  --> src/notes/op_rhs.rs:33:21
   |
33 |         let a3 = a1 + a2;
   |                  -- ^ -- std::vec::Vec<u32>
   |                  |
   |                  std::vec::Vec<u32>
   |
   = note: an implementation of `std::ops::Add` might be missing for `std::vec::Vec<u32>`

很遺憾,Rust 預設對 Vec<i32> 型別沒有實現 + 的運算子過載。那我們嘗試自己手動實現吧。

由於 Rust 的 Orphan 規則,我們不能對標準庫中的 Vec 實現 Add trait,因此,必須得用自己定義的型別包裝一下:

#[derive(Debug, PartialEq)]
struct MyVec(Vec<u32>);

針對 MyVec 型別 Add trait,即實現 add 方法:

fn add(self, other: MyVec) -> MyVec {
    if self.0.is_empty() {
        return other;
    }
    let mut v3: Vec<u32> = Vec::new();
    self.0.iter().for_each(|x| v3.push(*x));
    let len1 = self.0.len();
    let len2 = other.0.len();
    if len1 < len2 {
        for i in len1..len2 {
            v3.push(other.0[i]);
        }
    }
    MyVec(v3)
}

最後,寫一個測試用例:

#[test]
fn test_vec_plus() {
    let a1: MyVec = MyVec(vec![1,2,3]);
    let a2: MyVec = MyVec(vec![1,2,4,5,6]);
    let a3: MyVec = a1 + a2;
    assert_eq!(a3, MyVec(vec![1,2,3,5,6]))
}

完整原始碼參考這裡

參考資料