特徵(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 — 型別轉換#

FromInto 是一對:實作 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