2016年3月30日 星期三

Rust 中實作型別運算子重載

最近在實作computation books第九章,用到很多Rust運算子重載的部分
運算子重載嘛,可以對自己定義的struct 或是enum 使用運算子,這樣就能寫出Vec3 + Vec3 這樣比較漂亮的寫法,不用Vec3.add(Vec3),啊雖然兩個本質上沒什麼兩樣啦
這章定義一個Sign的類別,分為<正>、<負>、<零>和<未知>,我們要實作他的乘法和加法:
enum Sign {
  POSITIVE,
  NEGATIVE,
  ZERO,
  UNKNOWN,
}

Rust可重載的運算子可以在這裡找到:
https://doc.rust-lang.org/std/ops/index.html
另外是比較運算子,包括PartialEq 跟PartialOrd:
https://doc.rust-lang.org/std/cmp/

例如我們要重載乘法運算子,以下是網站上的定義:
pub trait Mul<RHS = Self> {
  type Output;
  fn mul(self, rhs: RHS) -> Self::Output;
}

實作時當然就是先以use這個trait,然後實作這trait並加入相關的函式:
use std::ops::Mul;
impl Mul for Sign {
  type Output = Sign;
  fn mul(self, rhs: Self) -> Self {
    if self == Sign::ZERO || rhs == Sign::ZERO {
      Sign::ZERO
    } else if self == Sign::UNKNOWN || rhs == Sign::UNKNOWN {
      Sign::UNKNOWN
    } else if self == rhs {
      Sign::POSITIVE
    } else {
      Sign::NEGATIVE
    }
  }
}
這樣就完成了,現在我們就能這樣寫了:
assert_eq!(Sign::NEGATIVE, Sign::POSITIVE * Sign::NEGATIVE);

另外常遇到的問題是,將運算子重載和Rust 的泛型一起用時,例如,我們定義sum_of_square 這個function,並希望使用泛型:
fn inner_product<T: Copy>(lhs: T, rhs: T) -> T {
  lhs*lhs + rhs*rhs
}
這樣編譯並不會過,因為泛型T 並不適用乘法跟加法,我們需要告訴編譯器,只有實作Mul跟Add trait的型別才能通過。同時指定Output 型別同樣為T,文件上並沒有講如何實作這部分,我忘記是在哪找到要這樣寫的,就為了那個Output=T花了我超多時間RRRRR,反正Rust 的文件就是這樣…
fn inner_product<T: Mul<T, Output=T>+Add<T, Output=T>+Copy>
如果你要不同的實作,例如我要能夠跟i32 相加,那就是:
fn foo<T: Add<i32, Output=T>>(x: T) -> T { x+1 }

個人覺得:相較之下,C++運算子重載的語法真的相當的複雜(事實上我覺得我已經不會寫了Orz),rust 簡潔不少,使用trait 中的function name來實作也比C++ 用 operator+, operator* 好讀很多。

有關於文中所提Sign 的實作,可見:
https://github.com/yodalee/computationbook-rust/blob/master/programming_in_toyland/signs/sign.rs

沒有留言:

張貼留言