0x01 開篇
多態(tài)性(Polymorphism)在很多語言中都存在,比如Java/C#等。有了編程語言的多態(tài)性會使我們在工作中更加靈活和方便。當然,Rust也有多態(tài)性的特點。在Rust中有三種主要方法來實現(xiàn)多態(tài),我們的目的就是討論每種方法的優(yōu)缺點。文章總共分為三章。
下面以一個經(jīng)典的多態(tài)問題為例進行討論。
有三種形狀,矩形、等邊三角形,圓,分別求出他們的面積和周長。
0x02 枚舉
enum是Rust中的一種數(shù)據(jù)結(jié)構(gòu),我們可以將三種形狀保存到一個enum中。示例代碼如下:
enum Shape {
Rectangle { width: f32, height: f32 },
Triangle { side: f32 },
Circle { radius: f32 },
}
接下來為Shape增加兩個方法來分別計算周長和面積。示例代碼如下:
impl Shape {
/// 計算周長
pub fn perimeter(&self) -> f32 {
match self {
Shape::Rectangle { width, height } => width * 2.0 + height * 2.0,
Shape::Triangle { side } => side * 3.0,
Shape::Circle { radius } => radius * 2.0 * std::f32::consts::PI
}
}
/// 計算面積
pub fn area(&self) -> f32 {
match self {
Shape::Rectangle { width, height } => width * height,
Shape::Triangle { side } => side * 0.5 * 3.0_f32.sqrt() / 2.0 * side,
Shape::Circle { radius } => radius * radius * std::f32::consts::PI
}
}
}
最后,我們可以在代碼中使用它了。示例代碼如下:
/// 輸出周長
fn calc_perimeters(shape: Shape) {
println!("{}", shape.perimeter());
}
/// 輸出面積
fn calc_area(shape: Shape) {
println!("{}", shape.area());
}
優(yōu)點
使用enum可以將這些形狀全部放在同一塊內(nèi)存區(qū)域中。這是Rust中實現(xiàn)多態(tài)性最直接的一種方法。它具有下面一些優(yōu)勢:
- 它的數(shù)據(jù)結(jié)構(gòu)是內(nèi)聯(lián)的,它不需要通過其它引用來獲取它。
enum中所有的變量在內(nèi)存中都是連續(xù)存儲的,可以很容易檢索到他們。它可以快速定位變量在內(nèi)存的位置,這也是本文最重要的一個主題。 - 盡管數(shù)據(jù)是內(nèi)聯(lián)的,但集合中的每個項都可以從其相鄰項中獲取不同的變體。
- 可以將它們作為原始數(shù)據(jù)使用。
缺點
- 如果不同變體的大小存在很大的差異,則可能會浪費一些內(nèi)存。
- 枚舉一旦定義后,就不會被改變,且不能被擴展。這也是非常大的一個缺陷了。
0x03 Trait
直接先上代碼。
trait Shape {
fn perimeter(&self) -> f32;
fn area(&self) -> f32;
}
struct Rectangle { pub width: f32, pub height: f32 }
struct Triangle { pub side: f32 }
struct Circle { pub radius: f32 }
impl Shape for Rectangle {
fn perimeter(&self) -> f32 {
self.width * 2.0 + self.height * 2.0
}
fn area(&self) -> f32 {
self.width * self.height
}
}
impl Shape for Triangle {
fn perimeter(&self) -> f32 {
self.side * 3.0
}
fn area(&self) -> f32 {
self.side * 0.5 * 3.0_f32.sqrt() / 2.0 * self.side
}
}
impl Shape for Circle {
fn perimeter(&self) -> f32 {
self.radius * 2.0 * std::f32::consts::PI
}
fn area(&self) -> f32 {
self.radius * self.radius * std::f32::consts::PI
}
}
Trait可以翻譯為“特性”,“特征”,類似于其它GC語言中的接口或者協(xié)議,在Rust中也是一個多態(tài)的概念。Trait指定結(jié)構(gòu)體(Strut)必須實現(xiàn)的一組方法,然后它們可以為任意結(jié)構(gòu)體實現(xiàn),并且這些結(jié)構(gòu)可以在預期的特征中使用。
優(yōu)點
與枚舉相比,它們具有的一個主要優(yōu)點是,Trait可以被任意結(jié)構(gòu)體實現(xiàn),即使是不同的crate。當然,你也可以從其它crate中導入一個Trait,為你自己的結(jié)構(gòu)體去實現(xiàn)它,然后將該結(jié)構(gòu)體傳遞到需要該Trait的crate代碼中。另外,也可以選擇編寫僅接受某個Trait的代碼,這是使用枚舉所不能達到的效果。
缺點
不能快速通過Trait找到正在使用的變量并獲取它的其他屬性。
0x04 小結(jié)
本篇文章著重介紹了在Rust中兩種實現(xiàn)多態(tài)的方法——Enum和Trait。就上面兩種方法而言,我個人還是比較推薦使用Trait的。使用Trait的靈活性非常強大。其實說起Trait,Rust為我們提供了兩種選擇,這個就留在下一篇文章講了。