Trait對象和泛型

對比一下Trait對象和泛型:

  • Trait對象可以被看作一種數據類型,它總是以引用的方式被使用,在運行期間,它在棧中保存了具體類型的實例數據和實現自該Trait的方法。
  • 泛型不是一種數據類型,它可被看作是數據類型的參數形式或抽象形式,在編譯期間會被替換為具體的數據類型

Trait Objecct方式也稱為動態分派(dynamic dispatch),它在程序運行期間動態地決定具體類型。而Rust泛型是靜態分派,它在編譯期間會代碼膨脹,將泛型參數轉變為使用到的每種具體類型。

例如,類型Square和類型Rectangle都實現了Trait Area以及方法get_area,現在要創建一個vec,這個vec中包含了任意能夠調用get_area方法的類型實例。這種需求建議採用Trait Object方式:

fn main(){
  let mut sharps: Vec<&dyn Area> = vec![];
  sharps.push(&Square(3.0));
  sharps.push(&Rectangle(3.0, 2.0));
  println!("{}", sharps[0].get_area());
  println!("{}", sharps[1].get_area());
}

trait Area{
  fn get_area(&self)->f64;
}

struct Square(f64);
struct Rectangle(f64, f64);
impl Area for Square{
  fn get_area(&self) -> f64 {self.0 * self.0}
}
impl Area for Rectangle{
  fn get_area(&self) -> f64 {self.0 * self.1}
}

在上面的示例中,Vec sharps用於保存多種不同類型的數據,只要能調用get_area方法的數據都能存放在此,而調用get_area方法的能力,來自於Area Trait。因此,使用動態的類型dyn Area來描述所有這類數據。當sharps中任意一個數據要調用get_area方法時,都會從它的vtable中查找該方法,然後調用。

但如果改一下上面示例的需求,不僅要為f64實現上述功能,還要為i32、f32、u8等類型實現上述功能,這時候使用Trait Object就很冗餘了,要為每一個數值類型都實現一次。

使用泛型則可以解決這類因數據類型而導致的冗餘問題。

fn main(){
  let sharps: Vec<Sharp<_>> = vec![
    Sharp::Square(3.0_f64),
    Sharp::Rectangle(3.0_f64, 2.0_f64),
  ];
  sharps[0].get_area();
}

trait Area<T> {
  fn get_area(&self) -> T;
}

enum Sharp<T>{
  Square(T),
  Rectangle(T, T),
}

impl<T> Area<T> for Sharp<T>
  where T: Mul<Output=T> + Clone + Copy
{
  fn get_area(&self) -> T {
    match *self {
      Sharp::Rectangle(a, b) => return a * b,
      Sharp::Square(a) => return a * a,
    }
  }
}

上面使用了泛型枚舉,在這個枚舉類型上實現Area Trait,就可以讓泛型枚舉統一各種類型,使得這些類型的數據都具有get_area方法。