跳轉到

3.1 泛型與 Trait#

用 trait 表達「能力」#

Trait(特徵) 是 Rust 表達抽象的方式,類似其他語言的 interface:定義「一個型別必須提供哪些方法」:

trait Summary {
    fn summarize(&self) -> String;   // 必須實作

    fn preview(&self) -> String {    // 預設實作(可不覆寫)
        format!("(預覽:{})", self.summarize())
    }
}

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

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{}", self.title)
    }
}

fn main() {
    let a = Article { title: String::from("Rust 入門"), body: String::new() };
    println!("{}", a.summarize());
    println!("{}", a.preview());   // 用預設實作
}

泛型:型別的參數化#

// 泛型函式:T 是任何型別
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut max = &list[0];
    for item in list {
        if item > max {
            max = item;
        }
    }
    max
}

注意 T: PartialOrd——trait bound(特徵約束):告訴編譯器「T 必須能比大小」。泛型幾乎總是伴隨 trait bound,兩者是一體的。

trait bound 的兩種寫法#

// 寫法一:impl Trait(簡潔,最常用)
fn notify(item: &impl Summary) {
    println!("{}", item.summarize());
}

// 寫法二:泛型 + trait bound(需要多參數同型別時)
fn notify2<T: Summary>(item: &T) { ... }

// 多重約束用 +
fn show(item: &(impl Summary + Display)) { ... }

// where 子句(約束很多時比較好讀)
fn process<T>(item: &T) -> String
where
    T: Summary + Clone,
{
    item.summarize()
}

動態分發 vs 靜態分發#

同一個抽象,兩種實現方式:

// 靜態分發:編譯期為每個型別生成專屬程式碼(單態化),零開銷
fn notify(item: &impl Summary) { ... }

// 動態分發:trait object,執行期查表,允許混放不同型別
fn notify_all(items: &[Box<dyn Summary>]) {
    for item in items {
        println!("{}", item.summarize());
    }
}
impl Trait / 泛型 dyn Trait
分發 編譯期(單態化) 執行期(vtable)
效能 零開銷 一次指標查找
彈性 編譯期固定型別 可以混放不同型別、 delayed 選擇

慣用法:預設用泛型(零開銷),需要「執行期才決定型別」或「容器混放多型別」時才用 dyn Trait。

常用標準庫 trait#

你會不斷遇到這些標準庫 trait,記住它們的意義:

  • Display:可以用 {} 印出(要自己 impl)
  • Debug:可以用 {:?} 印出(多數型別用 #[derive(Debug)] 自動獲得)
  • Clone / Copy:可以複製(複習 2.2 節)
  • PartialOrd / Eq:可以比較
  • Default:有預設值
#[derive(Debug, Clone)]     // derive:讓編譯器自動實作
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 1, y: 2 };
    println!("{:?}", p);       // Point { x: 1, y: 2 }
}

本章小結#

  • Trait 定義能力,impl 為型別實作,可帶預設實作
  • 泛型 + trait bound 是一體的:fn f<T: Trait> 或 impl Trait
  • 靜態分發(泛型,零開銷)vs 動態分發(dyn Trait,執行期彈性)
  • 常用標準庫 trait:Display、Debug、Clone、Default
  • 多數 trait 可用 #[derive(...)] 自動實作

想一想#

  1. trait 和其他語言的 interface 有什麼異同?預設實作有什麼好處?
  2. 為什麼泛型「零開銷」?單態化是什麼意思?
  3. 什麼情況該用 dyn Trait?為什麼不預設用它?