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(...)]自動實作
想一想#
- trait 和其他語言的 interface 有什麼異同?預設實作有什麼好處?
- 為什麼泛型「零開銷」?單態化是什麼意思?
- 什麼情況該用
dyn Trait?為什麼不預設用它?