Rust Traits 白話解釋:與 C++ 的比較
什麼是 Trait?
Trait 就像是一個「能力證書」或「技能清單」。
想像一下:
- 你去考駕照,駕照就是一個 trait,它證明你有「開車」的能力
- 不管你開的是轎車、卡車還是機車,只要有駕照,就代表你具備了「開車」這個技能
- 不同的車輛類型都可以「實作」這個「開車」的 trait
基本範例:飛行能力
Rust 版本
// 定義一個 trait(能力清單) trait CanFly { fn fly(&self); } // 不同的東西都可以實作這個 trait struct Bird; struct Airplane; struct Superman; // 鳥類實作飛行能力 impl CanFly for Bird { fn fly(&self) { println!("用翅膀飛翔!"); } } // 飛機實作飛行能力 impl CanFly for Airplane { fn fly(&self) { println!("用引擎和機翼飛行!"); } } // 超人實作飛行能力 impl CanFly for Superman { fn fly(&self) { println!("用超能力飛行!"); } } // 使用這個 trait fn make_it_fly(thing: &dyn CanFly) { thing.fly(); // 不管是什麼東西,只要會飛就行 } fn main() { let bird = Bird; let airplane = Airplane; let superman = Superman; make_it_fly(&bird); // 用翅膀飛翔! make_it_fly(&airplane); // 用引擎和機翼飛行! make_it_fly(&superman); // 用超能力飛行! }
C++ 對應版本
#include <iostream>
#include <memory>
// C++ 版本 - 抽象類別(相當於 trait)
class CanFly {
public:
virtual void fly() = 0; // 純虛函數
virtual ~CanFly() = default;
};
class Bird : public CanFly {
public:
void fly() override {
std::cout << "用翅膀飛翔!" << std::endl;
}
};
class Airplane : public CanFly {
public:
void fly() override {
std::cout << "用引擎和機翼飛行!" << std::endl;
}
};
class Superman : public CanFly {
public:
void fly() override {
std::cout << "用超能力飛行!" << std::endl;
}
};
// 使用多型
void makeItFly(CanFly* thing) {
thing->fly();
}
int main() {
auto bird = std::make_unique<Bird>();
auto airplane = std::make_unique<Airplane>();
auto superman = std::make_unique<Superman>();
makeItFly(bird.get()); // 用翅膀飛翔!
makeItFly(airplane.get()); // 用引擎和機翼飛行!
makeItFly(superman.get()); // 用超能力飛行!
return 0;
}
Rust 與 C++ 的對應關係
| Rust | C++ | 說明 |
|---|---|---|
trait | interface (抽象類別) | 定義一組方法簽章 |
impl Trait for Type | class Type : public Interface | 實作介面 |
&dyn Trait | Interface* | 多型的使用方式 |
impl 區塊 | override 方法 | 實作具體功能 |
主要差異
1. 語法差異
- Rust:
trait+impl分離定義 - C++:
class+ 繼承一起定義
2. 實作方式
Rust 可以為已存在的型別「後加」trait:
// Rust 可以這樣做 impl CanFly for i32 { // 為內建型別 i32 加上飛行能力 fn fly(&self) { println!("數字 {} 神奇地飛起來了!", self); } } fn main() { let number = 42; number.fly(); // 數字 42 神奇地飛起來了! }
C++ 不能為內建型別後加介面:
// C++ 不能這樣做
// 你不能為內建的 int 類型後加介面
// int 必須在定義時就決定要繼承哪些類別
3. 記憶體管理
- Rust:
&dyn Trait是借用,自動管理記憶體 - C++:
Interface*需要手動管理記憶體或使用智慧指標
4. 多重能力
Rust 支援多個 trait:
#![allow(unused)] fn main() { trait CanWalk { fn walk(&self); } trait CanSwim { fn swim(&self); } // 可以同時實作多個 trait impl CanWalk for Duck { /* ... */ } impl CanSwim for Duck { /* ... */ } impl CanFly for Duck { /* ... */ } }
C++ 多重繼承:
class Duck : public CanWalk, public CanSwim, public CanFly {
// 需要實作所有純虛函數
};
實際例子:動物園管理系統
Rust 版本
// 定義各種能力 trait CanWalk { fn walk(&self); fn walk_speed(&self) -> u32; } trait CanSwim { fn swim(&self); fn swim_speed(&self) -> u32; } trait CanFly { fn fly(&self); fn fly_speed(&self) -> u32; } // 鴨子:會走、會遊、會飛 struct Duck { name: String, } impl CanWalk for Duck { fn walk(&self) { println!("鴨子 {} 用腳走路", self.name); } fn walk_speed(&self) -> u32 { 5 } } impl CanSwim for Duck { fn swim(&self) { println!("鴨子 {} 在水中游泳", self.name); } fn swim_speed(&self) -> u32 { 10 } } impl CanFly for Duck { fn fly(&self) { println!("鴨子 {} 拍翅膀飛行", self.name); } fn fly_speed(&self) -> u32 { 20 } } // 魚:只會游泳 struct Fish { name: String, } impl CanSwim for Fish { fn swim(&self) { println!("魚 {} 在水中游泳", self.name); } fn swim_speed(&self) -> u32 { 15 } } // 鳥:會走和飛 struct Eagle { name: String, } impl CanWalk for Eagle { fn walk(&self) { println!("老鷹 {} 用爪子走路", self.name); } fn walk_speed(&self) -> u32 { 3 } } impl CanFly for Eagle { fn fly(&self) { println!("老鷹 {} 展翅翱翔", self.name); } fn fly_speed(&self) -> u32 { 50 } } // 使用函數 fn swimming_competition(swimmers: Vec<&dyn CanSwim>) { println!("=== 游泳比賽開始 ==="); for swimmer in swimmers { swimmer.swim(); println!("速度: {} km/h", swimmer.swim_speed()); } } fn flying_show(flyers: Vec<&dyn CanFly>) { println!("=== 飛行表演開始 ==="); for flyer in flyers { flyer.fly(); println!("速度: {} km/h", flyer.fly_speed()); } } fn walking_parade(walkers: Vec<&dyn CanWalk>) { println!("=== 行走遊行開始 ==="); for walker in walkers { walker.walk(); println!("速度: {} km/h", walker.walk_speed()); } } fn main() { let duck = Duck { name: "唐老鴨".to_string() }; let fish = Fish { name: "尼莫".to_string() }; let eagle = Eagle { name: "老鷹".to_string() }; // 游泳比賽:鴨子和魚都可以參加 swimming_competition(vec![&duck, &fish]); println!(); // 飛行表演:鴨子和老鷹都可以參加 flying_show(vec![&duck, &eagle]); println!(); // 行走遊行:鴨子和老鷹都可以參加 walking_parade(vec![&duck, &eagle]); }
C++ 對應版本
#include <iostream>
#include <vector>
#include <memory>
#include <string>
// 抽象介面
class CanWalk {
public:
virtual void walk() = 0;
virtual int walk_speed() = 0;
virtual ~CanWalk() = default;
};
class CanSwim {
public:
virtual void swim() = 0;
virtual int swim_speed() = 0;
virtual ~CanSwim() = default;
};
class CanFly {
public:
virtual void fly() = 0;
virtual int fly_speed() = 0;
virtual ~CanFly() = default;
};
// 鴨子:多重繼承
class Duck : public CanWalk, public CanSwim, public CanFly {
private:
std::string name;
public:
Duck(const std::string& n) : name(n) {}
void walk() override {
std::cout << "鴨子 " << name << " 用腳走路" << std::endl;
}
int walk_speed() override { return 5; }
void swim() override {
std::cout << "鴨子 " << name << " 在水中游泳" << std::endl;
}
int swim_speed() override { return 10; }
void fly() override {
std::cout << "鴨子 " << name << " 拍翅膀飛行" << std::endl;
}
int fly_speed() override { return 20; }
};
// 魚:只繼承游泳
class Fish : public CanSwim {
private:
std::string name;
public:
Fish(const std::string& n) : name(n) {}
void swim() override {
std::cout << "魚 " << name << " 在水中游泳" << std::endl;
}
int swim_speed() override { return 15; }
};
// 老鷹:繼承走路和飛行
class Eagle : public CanWalk, public CanFly {
private:
std::string name;
public:
Eagle(const std::string& n) : name(n) {}
void walk() override {
std::cout << "老鷹 " << name << " 用爪子走路" << std::endl;
}
int walk_speed() override { return 3; }
void fly() override {
std::cout << "老鷹 " << name << " 展翅翱翔" << std::endl;
}
int fly_speed() override { return 50; }
};
// 使用函數
void swimming_competition(const std::vector<CanSwim*>& swimmers) {
std::cout << "=== 游泳比賽開始 ===" << std::endl;
for (auto swimmer : swimmers) {
swimmer->swim();
std::cout << "速度: " << swimmer->swim_speed() << " km/h" << std::endl;
}
}
void flying_show(const std::vector<CanFly*>& flyers) {
std::cout << "=== 飛行表演開始 ===" << std::endl;
for (auto flyer : flyers) {
flyer->fly();
std::cout << "速度: " << flyer->fly_speed() << " km/h" << std::endl;
}
}
void walking_parade(const std::vector<CanWalk*>& walkers) {
std::cout << "=== 行走遊行開始 ===" << std::endl;
for (auto walker : walkers) {
walker->walk();
std::cout << "速度: " << walker->walk_speed() << " km/h" << std::endl;
}
}
int main() {
auto duck = std::make_unique<Duck>("唐老鴨");
auto fish = std::make_unique<Fish>("尼莫");
auto eagle = std::make_unique<Eagle>("老鷹");
// 游泳比賽
swimming_competition({duck.get(), fish.get()});
std::cout << std::endl;
// 飛行表演
flying_show({duck.get(), eagle.get()});
std::cout << std::endl;
// 行走遊行
walking_parade({duck.get(), eagle.get()});
return 0;
}
常用的 Rust Traits
標準庫 Traits
#![allow(unused)] fn main() { // Debug - 用於 {:?} 格式化 #[derive(Debug)] struct Point { x: i32, y: i32 } // Clone - 用於 .clone() 方法 #[derive(Clone)] struct Data { value: String } // PartialEq - 用於 == 比較 #[derive(PartialEq)] struct Id(u32); // Display - 用於 {} 格式化 use std::fmt; impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } } }
自訂 Traits
#![allow(unused)] fn main() { // 定義計算面積的能力 trait Area { fn area(&self) -> f64; } struct Circle { radius: f64, } struct Rectangle { width: f64, height: f64, } impl Area for Circle { fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius } } impl Area for Rectangle { fn area(&self) -> f64 { self.width * self.height } } // 使用 trait fn print_area<T: Area>(shape: &T) { println!("面積: {}", shape.area()); } }
進階特性
Trait Bounds(特徵約束)
#![allow(unused)] fn main() { // 限制泛型必須實作特定 trait fn compare_and_print<T: PartialEq + Debug>(a: &T, b: &T) { if a == b { println!("{:?} 等於 {:?}", a, b); } else { println!("{:?} 不等於 {:?}", a, b); } } // 或者使用 where 子句 fn process_data<T>(data: &T) where T: Clone + Debug + PartialEq, { let cloned = data.clone(); println!("原始: {:?}", data); println!("複製: {:?}", cloned); println!("相等: {}", data == &cloned); } }
預設實作
#![allow(unused)] fn main() { trait Greet { fn name(&self) -> &str; // 預設實作 fn greet(&self) { println!("Hello, {}!", self.name()); } // 可以被覆寫的預設實作 fn goodbye(&self) { println!("Goodbye, {}!", self.name()); } } struct Person { name: String, } impl Greet for Person { fn name(&self) -> &str { &self.name } // 可以選擇覆寫預設實作 fn goodbye(&self) { println!("See you later, {}!", self.name()); } } }
總結
Rust Trait 的核心概念
- 像是技能證書:定義「能做什麼」
- 可以後加:為已存在的型別加上新能力
- 組合式設計:一個型別可以有多種能力
- 類型安全:編譯時就確保型別有對應的能力
與 C++ 的主要差異
- Rust 更靈活:可以為任何型別後加 trait
- C++ 更傳統:繼承式設計,必須在定義時決定
- Rust 記憶體更安全:自動管理記憶體,無需手動釋放
- C++ 效能更直接:虛函數表調用,但需要小心記憶體管理
使用建議
- 優先使用 derive:對於常用的 trait,儘量使用
#[derive(...)] - 組合勝於繼承:使用多個小 trait 組合,而不是大的 trait
- 為外部型別添加能力:利用 trait 的靈活性為第三方型別加功能
- 善用 trait bounds:在泛型中使用 trait 約束來確保型別安全
Trait 是 Rust 的核心特性,讓程式碼更模組化、更易於維護,同時保持高效能和型別安全!