Skip to content

建立 2026-09-19 更新 2026-09-19

泛型與 Trait

泛型(generics)讓同一份程式碼適用於多種型別;trait 則定義型別「能做什麼」,相當於其他語言的介面(interface)。兩者搭配使用,就能寫出既通用又型別安全的程式。

泛型

不用泛型時,找出最大值必須為每種型別各寫一個函式。用泛型參數 T 就能只寫一次:

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn main() {
    println!("{}", largest(&[34, 50, 25, 100, 65]));
    println!("{}", largest(&[1.5, 0.2, 9.9]));
    println!("{}", largest(&['a', 'z', 'm']));
}

T: PartialOrd 稱為 trait bound(trait 限制),表示「T 必須能夠比較大小」。沒有這個限制,編譯器無法保證 >T 有意義,就會拒絕編譯。

結構體與列舉也可以是泛型,標準函式庫的 Vec<T>Option<T> 都是如此:

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
    println!("{} {}", integer.x(), float.x());
}

Rust 在編譯時會為每一種實際使用的型別產生專屬的程式碼(稱為單型化, monomorphization),因此泛型執行時沒有額外成本。

Trait:定義共通行為

trait 是一組方法簽名。型別用 impl Trait for 型別 來實作它:

trait Summary {
    fn author(&self) -> String;

    // 預設實作:實作者可以直接沿用,也可以覆寫
    fn summarize(&self) -> String {
        format!("({} 的文章)", self.author())
    }
}

struct Article {
    author: String,
    title: String,
}

struct Tweet {
    username: String,
    content: String,
}

impl Summary for Article {
    fn author(&self) -> String {
        self.author.clone()
    }
}

impl Summary for Tweet {
    fn author(&self) -> String {
        format!("@{}", self.username)
    }

    fn summarize(&self) -> String {
        format!("{}:{}", self.author(), self.content)
    }
}

fn main() {
    let a = Article {
        author: String::from("小明"),
        title: String::from("認識 Rust"),
    };
    let t = Tweet {
        username: String::from("rustlang"),
        content: String::from("新版本發佈了"),
    };
    println!("{}({})", a.summarize(), a.title);
    println!("{}", t.summarize());
}

用 trait 當作參數限制

想接受「任何實作了 Summary 的型別」,有兩種等價寫法:

trait Summary {
    fn summarize(&self) -> String;
}

struct Note(String);

impl Summary for Note {
    fn summarize(&self) -> String {
        format!("筆記:{}", self.0)
    }
}

// 語法糖寫法
fn notify(item: &impl Summary) {
    println!("快訊!{}", item.summarize());
}

// 完整寫法,限制較多時用 where 較易讀
fn notify_generic<T>(item: &T)
where
    T: Summary,
{
    println!("快訊!{}", item.summarize());
}

fn main() {
    let note = Note(String::from("記得學 trait"));
    notify(&note);
    notify_generic(&note);
}

用 derive 自動實作常見 trait

標準函式庫的許多 trait 可以用 #[derive(...)] 自動產生實作:

Trait 功能
Debug 支援 {:?} 輸出
Clone 支援 .clone() 複製
Copy 賦值時自動複製而非移動(要求所有欄位都是 Copy
PartialEqEq 支援 ==!=
PartialOrdOrd 支援 <> 與排序
Hash 可以當作 HashMap 的鍵
Default 提供 Default::default() 預設值
#[derive(Debug, Clone, PartialEq, Default)]
struct Config {
    name: String,
    retries: u32,
}

fn main() {
    let a = Config::default();
    let b = a.clone();
    println!("{:?} {}", a, a == b);
}

靜態分派與動態分派

想把不同型別的物件放在同一個集合,要用 trait 物件(trait object)Box<dyn Trait>

trait Shape {
    fn area(&self) -> f64;
}

struct Circle(f64);
struct Square(f64);

impl Shape for Circle {
    fn area(&self) -> f64 {
        3.14159 * self.0 * self.0
    }
}

impl Shape for Square {
    fn area(&self) -> f64 {
        self.0 * self.0
    }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Circle(1.0)), Box::new(Square(2.0))];
    for s in &shapes {
        println!("面積 {:.2}", s.area());
    }
}
泛型/impl Trait dyn Trait
分派方式 靜態,編譯時決定 動態,執行時查表
效能 可完全內聯,最快 有微小的間接呼叫成本
能混合不同型別在同一集合 不行 可以

預設優先使用泛型;需要異質集合時再用 dyn Trait

推薦影音

泛型型別

簡述:Let's Get Rusty 依照官方書籍第 10 章製作,說明泛型函式、結構體與 trait bound 的寫法。

Trait

簡述:微軟《Beginner's Series to Rust》第 33 集,介紹如何定義與實作 trait。

下一頁:錯誤處理