泛型與 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(¬e);
notify_generic(¬e);
}
用 derive 自動實作常見 trait
標準函式庫的許多 trait 可以用 #[derive(...)] 自動產生實作:
| Trait | 功能 |
|---|---|
Debug |
支援 {:?} 輸出 |
Clone |
支援 .clone() 複製 |
Copy |
賦值時自動複製而非移動(要求所有欄位都是 Copy) |
PartialEq/Eq |
支援 == 與 != |
PartialOrd/Ord |
支援 <、> 與排序 |
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。
下一頁:錯誤處理。