Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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++ 的對應關係

RustC++說明
traitinterface (抽象類別)定義一組方法簽章
impl Trait for Typeclass Type : public Interface實作介面
&dyn TraitInterface*多型的使用方式
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++ 效能更直接:虛函數表調用,但需要小心記憶體管理

使用建議

  1. 優先使用 derive:對於常用的 trait,儘量使用 #[derive(...)]
  2. 組合勝於繼承:使用多個小 trait 組合,而不是大的 trait
  3. 為外部型別添加能力:利用 trait 的靈活性為第三方型別加功能
  4. 善用 trait bounds:在泛型中使用 trait 約束來確保型別安全

Trait 是 Rust 的核心特性,讓程式碼更模組化、更易於維護,同時保持高效能和型別安全!