特徵(Traits)#
特徵(Trait) 定義一組方法的介面,讓不同型別可以有共同的行為。
Trait 是 Rust 多型(Polymorphism)的核心機制,類似其他語言的「介面(Interface)」。
1. 定義與實作 Trait#
用 trait 關鍵字定義,用 impl Trait for Type 為型別實作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| trait Greet {
fn hello(&self) -> String;
}
struct Person {
name: String,
}
struct Robot {
id: u32,
}
impl Greet for Person {
fn hello(&self) -> String {
format!("你好,我是 {}!", self.name)
}
}
impl Greet for Robot {
fn hello(&self) -> String {
format!("系統啟動,編號 {}。", self.id)
}
}
fn main() {
let p = Person { name: String::from("Alice") };
let r = Robot { id: 42 };
println!("{}", p.hello()); // 你好,我是 Alice!
println!("{}", r.hello()); // 系統啟動,編號 42。
}
|
2. 預設實作#
Trait 可以提供方法的 預設實作 ,實作者可以選擇直接使用或覆寫:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| trait Summary {
fn author(&self) -> String;
// 預設實作,可覆寫
fn summarize(&self) -> String {
format!("(作者:{})", self.author())
}
}
struct Article {
title: String,
author: String,
}
impl Summary for Article {
fn author(&self) -> String {
self.author.clone()
}
// summarize 使用預設實作
}
struct Tweet {
username: String,
content: String,
}
impl Summary for Tweet {
fn author(&self) -> String {
format!("@{}", self.username)
}
// 覆寫預設實作
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn main() {
let article = Article {
title: String::from("Rust 入門"),
author: String::from("Alice"),
};
let tweet = Tweet {
username: String::from("bob"),
content: String::from("今天學了 Rust Traits!"),
};
println!("{}", article.summarize()); // (作者:Alice)
println!("{}", tweet.summarize()); // bob: 今天學了 Rust Traits!
}
|
3. Trait 作為參數#
用 impl Trait 接受任何實作了該 Trait 的型別:
1
2
3
| fn notify(item: &impl Summary) {
println!("通知:{}", item.summarize());
}
|
Trait Bound 語法#
impl Trait 是語法糖,完整寫法是 Trait Bound ,在泛型參數後面加上 ::
1
2
3
4
5
| // impl Trait 語法(簡潔,適合單一參數)
fn notify(item: &impl Summary) { /* ... */ }
// Trait Bound 語法(等效,多個泛型參數時更清晰)
fn notify<T: Summary>(item: &T) { /* ... */ }
|
多個 Trait Bound#
用 + 要求型別同時實作多個 Trait:
1
2
3
4
5
6
| use std::fmt::Display;
fn notify(item: &(impl Summary + Display)) { /* ... */ }
// 或 Trait Bound 寫法
fn notify<T: Summary + Display>(item: &T) { /* ... */ }
|
where 語法#
當 Trait Bound 很多時,用 where 讓簽名更易讀:
1
2
3
4
5
6
7
8
9
| // 難以閱讀
fn process<T: Summary + Clone, U: Display + PartialOrd>(t: &T, u: &U) { /* ... */ }
// 使用 where,結構更清晰
fn process<T, U>(t: &T, u: &U)
where
T: Summary + Clone,
U: Display + PartialOrd,
{ /* ... */ }
|
4. 回傳實作 Trait 的型別#
用 impl Trait 作為回傳型別,表示「回傳某個實作了此 Trait 的具體型別」:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| trait Summary {
fn summarize(&self) -> String;
}
struct Tweet { content: String }
impl Summary for Tweet {
fn summarize(&self) -> String { self.content.clone() }
}
fn make_summary(content: &str) -> impl Summary {
Tweet { content: String::from(content) }
}
fn main() {
let s = make_summary("Rust 很棒!");
println!("{}", s.summarize());
}
|
注意:此語法只能回傳 單一具體型別 。若需要在執行時決定回傳不同型別,請見第 7 節的 Trait 物件。
5. 常用標準函式庫 Trait#
Display — 格式化輸出#
實作 Display 後可以用 {} 列印:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| use std::fmt;
struct Point {
x: f64,
y: f64,
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let p = Point { x: 1.0, y: 2.5 };
println!("{p}"); // (1, 2.5)
println!("座標是 {p}"); // 座標是 (1, 2.5)
}
|
From 與 Into — 型別轉換#
From 和 Into 是一對:實作 From<T> 後,Into<U> 會自動可用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| struct Meters(f64);
struct Centimeters(f64);
impl From<Meters> for Centimeters {
fn from(m: Meters) -> Centimeters {
Centimeters(m.0 * 100.0)
}
}
fn main() {
let m = Meters(1.5);
let cm: Centimeters = m.into(); // Into 自動可用
println!("{}cm", cm.0); // 150cm
}
|
6. derive 巨集#
手動實作常見 Trait 很繁瑣,#[derive] 可以自動產生:
1
2
3
4
5
6
7
8
9
10
11
12
13
| #[derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}
fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1.clone();
println!("{:?}", p1); // Point { x: 1.0, y: 2.0 }
println!("{}", p1 == p2); // true
}
|
常用 derive 的 Trait:
| Trait | 功能 |
|---|
Debug | 允許 {:?} 列印,除錯用 |
Clone | 允許 .clone() 深度複製 |
Copy | 允許指派時自動複製(堆疊型別) |
PartialEq | 允許 == 和 != |
PartialOrd | 允許 <、> 比較 |
Default | 提供 Type::default() 零值 |
Hash | 允許作為 HashMap 的鍵 |
7. Trait 物件(dyn Trait)#
impl Trait 在編譯時決定型別(靜態分派,無額外成本)。若需要在執行時存放 不同型別 於同一集合,使用 Trait 物件 (動態分派):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| trait Greet {
fn hello(&self) -> String;
}
struct Person { name: String }
struct Robot { id: u32 }
impl Greet for Person {
fn hello(&self) -> String { format!("我是 {}", self.name) }
}
impl Greet for Robot {
fn hello(&self) -> String { format!("Robot #{}", self.id) }
}
fn main() {
// Box<dyn Trait> 讓 Vec 存放不同的具體型別
let greeters: Vec<Box<dyn Greet>> = vec![
Box::new(Person { name: String::from("Alice") }),
Box::new(Robot { id: 1 }),
];
for g in &greeters {
println!("{}", g.hello());
}
// 我是 Alice
// Robot #1
}
|
Box<dyn Trait> 透過指標在執行時找到正確的方法,有少量的動態分派開銷;impl Trait 在編譯時確定,無額外開銷。
Reference#
https://doc.rust-lang.org/book/ch10-02-traits.html