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
方法。