Rust 高級(jí) trait

2023-03-22 15:16 更新
ch19-03-advanced-traits.md
commit 81d05c9a6d06d79f2a85c8ea184f41dc82532d98

第十章 “trait:定義共享的行為” 部分,我們第一次涉及到了 trait,不過就像生命周期一樣,我們并沒有覆蓋一些較為高級(jí)的細(xì)節(jié)?,F(xiàn)在我們更加了解 Rust 了,可以深入理解其本質(zhì)了。

關(guān)聯(lián)類型在 trait 定義中指定占位符類型

關(guān)聯(lián)類型associated types)是一個(gè)將類型占位符與 trait 相關(guān)聯(lián)的方式,這樣 trait 的方法簽名中就可以使用這些占位符類型。trait 的實(shí)現(xiàn)者會(huì)針對(duì)特定的實(shí)現(xiàn)在這個(gè)類型的位置指定相應(yīng)的具體類型。如此可以定義一個(gè)使用多種類型的 trait,直到實(shí)現(xiàn)此 trait 時(shí)都無需知道這些類型具體是什么。

本章所描述的大部分內(nèi)容都非常少見。關(guān)聯(lián)類型則比較適中;它們比本書其他的內(nèi)容要少見,不過比本章中的很多內(nèi)容要更常見。

一個(gè)帶有關(guān)聯(lián)類型的 trait 的例子是標(biāo)準(zhǔn)庫提供的 Iterator trait。它有一個(gè)叫做 Item 的關(guān)聯(lián)類型來替代遍歷的值的類型。第十三章的 “Iterator trait 和 next 方法” 部分曾提到過 Iterator trait 的定義如示例 19-12 所示:

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}

示例 19-12: Iterator trait 的定義中帶有關(guān)聯(lián)類型 Item

Item 是一個(gè)占位類型,同時(shí) next 方法定義表明它返回 Option<Self::Item> 類型的值。這個(gè) trait 的實(shí)現(xiàn)者會(huì)指定 Item 的具體類型,然而不管實(shí)現(xiàn)者指定何種類型, next 方法都會(huì)返回一個(gè)包含了此具體類型值的 Option

關(guān)聯(lián)類型看起來像一個(gè)類似泛型的概念,因?yàn)樗试S定義一個(gè)函數(shù)而不指定其可以處理的類型。那么為什么要使用關(guān)聯(lián)類型呢?

讓我們通過一個(gè)在第十三章中出現(xiàn)的 Counter 結(jié)構(gòu)體上實(shí)現(xiàn) Iterator trait 的例子來檢視其中的區(qū)別。在示例 13-21 中,指定了 Item 的類型為 u32

文件名: src/lib.rs

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        // --snip--

這類似于泛型。那么為什么 Iterator trait 不像示例 19-13 那樣定義呢?

pub trait Iterator<T> {
    fn next(&mut self) -> Option<T>;
}

示例 19-13: 一個(gè)使用泛型的 Iterator trait 假想定義

區(qū)別在于當(dāng)如示例 19-13 那樣使用泛型時(shí),則不得不在每一個(gè)實(shí)現(xiàn)中標(biāo)注類型。這是因?yàn)槲覀円部梢詫?shí)現(xiàn)為 Iterator<String> for Counter,或任何其他類型,這樣就可以有多個(gè) Counter 的 Iterator 的實(shí)現(xiàn)。換句話說,當(dāng) trait 有泛型參數(shù)時(shí),可以多次實(shí)現(xiàn)這個(gè) trait,每次需改變泛型參數(shù)的具體類型。接著當(dāng)使用 Counter 的 next 方法時(shí),必須提供類型注解來表明希望使用 Iterator 的哪一個(gè)實(shí)現(xiàn)。

通過關(guān)聯(lián)類型,則無需標(biāo)注類型,因?yàn)椴荒芏啻螌?shí)現(xiàn)這個(gè) trait。對(duì)于示例 19-12 使用關(guān)聯(lián)類型的定義,我們只能選擇一次 Item 會(huì)是什么類型,因?yàn)橹荒苡幸粋€(gè) impl Iterator for Counter。當(dāng)調(diào)用 Counter 的 next 時(shí)不必每次指定我們需要 u32 值的迭代器。

默認(rèn)泛型類型參數(shù)和運(yùn)算符重載

當(dāng)使用泛型類型參數(shù)時(shí),可以為泛型指定一個(gè)默認(rèn)的具體類型。如果默認(rèn)類型就足夠的話,這消除了為具體類型實(shí)現(xiàn) trait 的需要。為泛型類型指定默認(rèn)類型的語法是在聲明泛型類型時(shí)使用 <PlaceholderType=ConcreteType>。

這種情況的一個(gè)非常好的例子是用于運(yùn)算符重載。運(yùn)算符重載Operator overloading)是指在特定情況下自定義運(yùn)算符(比如 +)行為的操作。

Rust 并不允許創(chuàng)建自定義運(yùn)算符或重載任意運(yùn)算符,不過 std::ops 中所列出的運(yùn)算符和相應(yīng)的 trait 可以通過實(shí)現(xiàn)運(yùn)算符相關(guān) trait 來重載。例如,示例 19-14 中展示了如何在 Point 結(jié)構(gòu)體上實(shí)現(xiàn) Add trait 來重載 + 運(yùn)算符,這樣就可以將兩個(gè) Point 實(shí)例相加了:

文件名: src/main.rs

use std::ops::Add;

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    assert_eq!(
        Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
        Point { x: 3, y: 3 }
    );
}

示例 19-14: 實(shí)現(xiàn) Add trait 重載 Point 實(shí)例的 + 運(yùn)算符

add 方法將兩個(gè) Point 實(shí)例的 x 值和 y 值分別相加來創(chuàng)建一個(gè)新的 PointAdd trait 有一個(gè)叫做 Output 的關(guān)聯(lián)類型,它用來決定 add 方法的返回值類型。

這里默認(rèn)泛型類型位于 Add trait 中。這里是其定義:

trait Add<Rhs=Self> {
    type Output;

    fn add(self, rhs: Rhs) -> Self::Output;
}

這些代碼看來應(yīng)該很熟悉,這是一個(gè)帶有一個(gè)方法和一個(gè)關(guān)聯(lián)類型的 trait。比較陌生的部分是尖括號(hào)中的 Rhs=Self:這個(gè)語法叫做 默認(rèn)類型參數(shù)default type parameters)。Rhs 是一個(gè)泛型類型參數(shù)(“right hand side” 的縮寫),它用于定義 add 方法中的 rhs 參數(shù)。如果實(shí)現(xiàn) Add trait 時(shí)不指定 Rhs 的具體類型,Rhs 的類型將是默認(rèn)的 Self 類型,也就是在其上實(shí)現(xiàn) Add 的類型。

當(dāng)為 Point 實(shí)現(xiàn) Add 時(shí),使用了默認(rèn)的 Rhs,因?yàn)槲覀兿M麑蓚€(gè) Point 實(shí)例相加。讓我們看看一個(gè)實(shí)現(xiàn) Add trait 時(shí)希望自定義 Rhs 類型而不是使用默認(rèn)類型的例子。

這里有兩個(gè)存放不同單元值的結(jié)構(gòu)體,Millimeters 和 Meters。(這種將現(xiàn)有類型簡單封裝進(jìn)另一個(gè)結(jié)構(gòu)體的方式被稱為 newtype 模式newtype pattern,之后的 “為了類型安全和抽象而使用 newtype 模式” 部分會(huì)詳細(xì)介紹。)我們希望能夠?qū)⒑撩字蹬c米值相加,并讓 Add 的實(shí)現(xiàn)正確處理轉(zhuǎn)換??梢詾?nbsp;Millimeters 實(shí)現(xiàn) Add 并以 Meters 作為 Rhs,如示例 19-15 所示。

文件名: src/lib.rs

use std::ops::Add;

struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters> for Millimeters {
    type Output = Millimeters;

    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}

示例 19-15: 在 Millimeters 上實(shí)現(xiàn) Add,以便能夠?qū)?nbsp;Millimeters 與 Meters 相加

為了使 Millimeters 和 Meters 能夠相加,我們指定 impl Add<Meters> 來設(shè)定 Rhs 類型參數(shù)的值而不是使用默認(rèn)的 Self。

默認(rèn)參數(shù)類型主要用于如下兩個(gè)方面:

  • 擴(kuò)展類型而不破壞現(xiàn)有代碼。
  • 在大部分用戶都不需要的特定情況進(jìn)行自定義。

標(biāo)準(zhǔn)庫的 Add trait 就是一個(gè)第二個(gè)目的例子:大部分時(shí)候你會(huì)將兩個(gè)相似的類型相加,不過它提供了自定義額外行為的能力。在 Add trait 定義中使用默認(rèn)類型參數(shù)意味著大部分時(shí)候無需指定額外的參數(shù)。換句話說,一小部分實(shí)現(xiàn)的樣板代碼是不必要的,這樣使用 trait 就更容易了。

第一個(gè)目的是相似的,但過程是反過來的:如果需要為現(xiàn)有 trait 增加類型參數(shù),為其提供一個(gè)默認(rèn)類型將允許我們?cè)诓黄茐默F(xiàn)有實(shí)現(xiàn)代碼的基礎(chǔ)上擴(kuò)展 trait 的功能。

完全限定語法與消歧義:調(diào)用相同名稱的方法

Rust 既不能避免一個(gè) trait 與另一個(gè) trait 擁有相同名稱的方法,也不能阻止為同一類型同時(shí)實(shí)現(xiàn)這兩個(gè) trait。甚至直接在類型上實(shí)現(xiàn)開始已經(jīng)有的同名方法也是可能的!

不過,當(dāng)調(diào)用這些同名方法時(shí),需要告訴 Rust 我們希望使用哪一個(gè)。考慮一下示例 19-16 中的代碼,這里定義了 trait Pilot 和 Wizard 都擁有方法 fly。接著在一個(gè)本身已經(jīng)實(shí)現(xiàn)了名為 fly 方法的類型 Human 上實(shí)現(xiàn)這兩個(gè) trait。每一個(gè) fly 方法都進(jìn)行了不同的操作:

文件名: src/main.rs

trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}

impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}

示例 19-16: 兩個(gè) trait 定義為擁有 fly 方法,并在直接定義有 fly 方法的 Human 類型上實(shí)現(xiàn)這兩個(gè) trait

當(dāng)調(diào)用 Human 實(shí)例的 fly 時(shí),編譯器默認(rèn)調(diào)用直接實(shí)現(xiàn)在類型上的方法,如示例 19-17 所示。

文件名: src/main.rs

fn main() {
    let person = Human;
    person.fly();
}

示例 19-17: 調(diào)用 Human 實(shí)例的 fly

運(yùn)行這段代碼會(huì)打印出 *waving arms furiously*,這表明 Rust 調(diào)用了直接實(shí)現(xiàn)在 Human 上的 fly 方法。

為了能夠調(diào)用 Pilot trait 或 Wizard trait 的 fly 方法,我們需要使用更明顯的語法以便能指定我們指的是哪個(gè) fly 方法。這個(gè)語法展示在示例 19-18 中:

文件名: src/main.rs

fn main() {
    let person = Human;
    Pilot::fly(&person);
    Wizard::fly(&person);
    person.fly();
}

示例 19-18: 指定我們希望調(diào)用哪一個(gè) trait 的 fly 方法

在方法名前指定 trait 名向 Rust 澄清了我們希望調(diào)用哪個(gè) fly 實(shí)現(xiàn)。也可以選擇寫成 Human::fly(&person),這等同于示例 19-18 中的 person.fly(),不過如果無需消歧義的話這么寫就有點(diǎn)長了。

運(yùn)行這段代碼會(huì)打印出:

$ cargo run
   Compiling traits-example v0.1.0 (file:///projects/traits-example)
    Finished dev [unoptimized + debuginfo] target(s) in 0.46s
     Running `target/debug/traits-example`
This is your captain speaking.
Up!
*waving arms furiously*

因?yàn)?nbsp;fly 方法獲取一個(gè) self 參數(shù),如果有兩個(gè) 類型 都實(shí)現(xiàn)了同一 trait,Rust 可以根據(jù) self 的類型計(jì)算出應(yīng)該使用哪一個(gè) trait 實(shí)現(xiàn)。

然而,關(guān)聯(lián)函數(shù)是 trait 的一部分,但沒有 self 參數(shù)。當(dāng)同一作用域的兩個(gè)類型實(shí)現(xiàn)了同一 trait,Rust 就不能計(jì)算出我們期望的是哪一個(gè)類型,除非使用 完全限定語法fully qualified syntax)。例如,拿示例 19-19 中的 Animal trait 來說,它有關(guān)聯(lián)函數(shù) baby_name,結(jié)構(gòu)體 Dog 實(shí)現(xiàn)了 Animal,同時(shí)有關(guān)聯(lián)函數(shù) baby_name 直接定義于 Dog 之上:

文件名: src/main.rs

trait Animal {
    fn baby_name() -> String;
}

struct Dog;

impl Dog {
    fn baby_name() -> String {
        String::from("Spot")
    }
}

impl Animal for Dog {
    fn baby_name() -> String {
        String::from("puppy")
    }
}

fn main() {
    println!("A baby dog is called a {}", Dog::baby_name());
}

示例 19-19: 一個(gè)帶有關(guān)聯(lián)函數(shù)的 trait 和一個(gè)帶有同名關(guān)聯(lián)函數(shù)并實(shí)現(xiàn)了此 trait 的類型

這段代碼用于一個(gè)動(dòng)物收容所,他們將所有的小狗起名為 Spot,這實(shí)現(xiàn)為定義于 Dog 之上的關(guān)聯(lián)函數(shù) baby_name。Dog 類型還實(shí)現(xiàn)了 Animal trait,它描述了所有動(dòng)物的共有的特征。小狗被稱為 puppy,這表現(xiàn)為 Dog 的 Animal trait 實(shí)現(xiàn)中與 Animal trait 相關(guān)聯(lián)的函數(shù) baby_name。

在 main 調(diào)用了 Dog::baby_name 函數(shù),它直接調(diào)用了定義于 Dog 之上的關(guān)聯(lián)函數(shù)。這段代碼會(huì)打印出:

$ cargo run
   Compiling traits-example v0.1.0 (file:///projects/traits-example)
    Finished dev [unoptimized + debuginfo] target(s) in 0.54s
     Running `target/debug/traits-example`
A baby dog is called a Spot

這并不是我們需要的。我們希望調(diào)用的是 Dog 上 Animal trait 實(shí)現(xiàn)那部分的 baby_name 函數(shù),這樣能夠打印出 A baby dog is called a puppy。示例 19-18 中用到的技術(shù)在這并不管用;如果將 main 改為示例 19-20 中的代碼,則會(huì)得到一個(gè)編譯錯(cuò)誤:

文件名: src/main.rs

fn main() {
    println!("A baby dog is called a {}", Animal::baby_name());
}

示例 19-20: 嘗試調(diào)用 Animal trait 的 baby_name 函數(shù),不過 Rust 并不知道該使用哪一個(gè)實(shí)現(xiàn)

因?yàn)?nbsp;Animal::baby_name 是關(guān)聯(lián)函數(shù)而不是方法,因此它沒有 self 參數(shù),Rust 無法計(jì)算出所需的是哪一個(gè) Animal::baby_name 實(shí)現(xiàn)。我們會(huì)得到這個(gè)編譯錯(cuò)誤:

$ cargo run
   Compiling traits-example v0.1.0 (file:///projects/traits-example)
error[E0283]: type annotations needed
  --> src/main.rs:20:43
   |
20 |     println!("A baby dog is called a {}", Animal::baby_name());
   |                                           ^^^^^^^^^^^^^^^^^ cannot infer type
   |
   = note: cannot satisfy `_: Animal`

For more information about this error, try `rustc --explain E0283`.
error: could not compile `traits-example` due to previous error

為了消歧義并告訴 Rust 我們希望使用的是 Dog 的 Animal 實(shí)現(xiàn),需要使用 完全限定語法,這是調(diào)用函數(shù)時(shí)最為明確的方式。示例 19-21 展示了如何使用完全限定語法:

文件名: src/main.rs

fn main() {
    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

示例 19-21: 使用完全限定語法來指定我們希望調(diào)用的是 Dog 上 Animal trait 實(shí)現(xiàn)中的 baby_name 函數(shù)

我們?cè)诩饫ㄌ?hào)中向 Rust 提供了類型注解,并通過在此函數(shù)調(diào)用中將 Dog 類型當(dāng)作 Animal 對(duì)待,來指定希望調(diào)用的是 Dog 上 Animal trait 實(shí)現(xiàn)中的 baby_name 函數(shù)?,F(xiàn)在這段代碼會(huì)打印出我們期望的數(shù)據(jù):

$ cargo run
   Compiling traits-example v0.1.0 (file:///projects/traits-example)
    Finished dev [unoptimized + debuginfo] target(s) in 0.48s
     Running `target/debug/traits-example`
A baby dog is called a puppy

通常,完全限定語法定義為:

<Type as Trait>::function(receiver_if_method, next_arg, ...);

對(duì)于關(guān)聯(lián)函數(shù),其沒有一個(gè) receiver,故只會(huì)有其他參數(shù)的列表??梢赃x擇在任何函數(shù)或方法調(diào)用處使用完全限定語法。然而,允許省略任何 Rust 能夠從程序中的其他信息中計(jì)算出的部分。只有當(dāng)存在多個(gè)同名實(shí)現(xiàn)而 Rust 需要幫助以便知道我們希望調(diào)用哪個(gè)實(shí)現(xiàn)時(shí),才需要使用這個(gè)較為冗長的語法。

父 trait 用于在另一個(gè) trait 中使用某 trait 的功能

有時(shí)我們可能會(huì)需要某個(gè) trait 使用另一個(gè) trait 的功能。在這種情況下,需要能夠依賴相關(guān)的 trait 也被實(shí)現(xiàn)。這個(gè)所需的 trait 是我們實(shí)現(xiàn)的 trait 的 父(超) traitsupertrait)。

例如我們希望創(chuàng)建一個(gè)帶有 outline_print 方法的 trait OutlinePrint,它會(huì)打印出帶有星號(hào)框的值。也就是說,如果 Point 實(shí)現(xiàn)了 Display 并返回 (x, y),調(diào)用以 1 作為 x 和 3 作為 y 的 Point 實(shí)例的 outline_print 會(huì)顯示如下:

**********
*        *
* (1, 3) *
*        *
**********

在 outline_print 的實(shí)現(xiàn)中,因?yàn)橄M軌蚴褂?nbsp;Display trait 的功能,則需要說明 OutlinePrint 只能用于同時(shí)也實(shí)現(xiàn)了 Display 并提供了 OutlinePrint 需要的功能的類型??梢酝ㄟ^在 trait 定義中指定 OutlinePrint: Display 來做到這一點(diǎn)。這類似于為 trait 增加 trait bound。示例 19-22 展示了一個(gè) OutlinePrint trait 的實(shí)現(xiàn):

文件名: src/main.rs

use std::fmt;

trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("*{}*", " ".repeat(len + 2));
        println!("* {} *", output);
        println!("*{}*", " ".repeat(len + 2));
        println!("{}", "*".repeat(len + 4));
    }
}

示例 19-22: 實(shí)現(xiàn) OutlinePrint trait,它要求來自 Display 的功能

因?yàn)橹付?nbsp;OutlinePrint 需要 Display trait,則可以在 outline_print 中使用 to_string, 其會(huì)為任何實(shí)現(xiàn) Display 的類型自動(dòng)實(shí)現(xiàn)。如果不在 trait 名后增加 : Display 并嘗試在 outline_print 中使用 to_string,則會(huì)得到一個(gè)錯(cuò)誤說在當(dāng)前作用域中沒有找到用于 &Self 類型的方法 to_string。

讓我們看看如果嘗試在一個(gè)沒有實(shí)現(xiàn) Display 的類型上實(shí)現(xiàn) OutlinePrint 會(huì)發(fā)生什么,比如 Point 結(jié)構(gòu)體:

文件名: src/main.rs

struct Point {
    x: i32,
    y: i32,
}

impl OutlinePrint for Point {}

這樣會(huì)得到一個(gè)錯(cuò)誤說 Display 是必須的而未被實(shí)現(xiàn):

$ cargo run
   Compiling traits-example v0.1.0 (file:///projects/traits-example)
error[E0277]: `Point` doesn't implement `std::fmt::Display`
  --> src/main.rs:20:6
   |
20 | impl OutlinePrint for Point {}
   |      ^^^^^^^^^^^^ `Point` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `Point`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
note: required by a bound in `OutlinePrint`
  --> src/main.rs:3:21
   |
3  | trait OutlinePrint: fmt::Display {
   |                     ^^^^^^^^^^^^ required by this bound in `OutlinePrint`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `traits-example` due to previous error

一旦在 Point 上實(shí)現(xiàn) Display 并滿足 OutlinePrint 要求的限制,比如這樣:

文件名: src/main.rs

use std::fmt;

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

那么在 Point 上實(shí)現(xiàn) OutlinePrint trait 將能成功編譯,并可以在 Point 實(shí)例上調(diào)用 outline_print 來顯示位于星號(hào)框中的點(diǎn)的值。

newtype 模式用以在外部類型上實(shí)現(xiàn)外部 trait

在第十章的 “為類型實(shí)現(xiàn) trait” 部分,我們提到了孤兒規(guī)則(orphan rule),它說明只要 trait 或類型對(duì)于當(dāng)前 crate 是本地的話就可以在此類型上實(shí)現(xiàn)該 trait。一個(gè)繞開這個(gè)限制的方法是使用 newtype 模式newtype pattern),它涉及到在一個(gè)元組結(jié)構(gòu)體(第五章 “用沒有命名字段的元組結(jié)構(gòu)體來創(chuàng)建不同的類型” 部分介紹了元組結(jié)構(gòu)體)中創(chuàng)建一個(gè)新類型。這個(gè)元組結(jié)構(gòu)體帶有一個(gè)字段作為希望實(shí)現(xiàn) trait 的類型的簡單封裝。接著這個(gè)封裝類型對(duì)于 crate 是本地的,這樣就可以在這個(gè)封裝上實(shí)現(xiàn) trait。Newtype 是一個(gè)源自 (U.C.0079,逃) Haskell 編程語言的概念。使用這個(gè)模式?jīng)]有運(yùn)行時(shí)性能懲罰,這個(gè)封裝類型在編譯時(shí)就被省略了。

例如,如果想要在 Vec<T> 上實(shí)現(xiàn) Display,而孤兒規(guī)則阻止我們直接這么做,因?yàn)?nbsp;Display trait 和 Vec<T> 都定義于我們的 crate 之外。可以創(chuàng)建一個(gè)包含 Vec<T> 實(shí)例的 Wrapper 結(jié)構(gòu)體,接著可以如列表 19-23 那樣在 Wrapper 上實(shí)現(xiàn) Display 并使用 Vec<T> 的值:

文件名: src/main.rs

use std::fmt;

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

fn main() {
    let w = Wrapper(vec![String::from("hello"), String::from("world")]);
    println!("w = {}", w);
}

示例 19-23: 創(chuàng)建 Wrapper 類型封裝 Vec<String> 以便能夠?qū)崿F(xiàn) Display

Display 的實(shí)現(xiàn)使用 self.0 來訪問其內(nèi)部的 Vec<T>,因?yàn)?nbsp;Wrapper 是元組結(jié)構(gòu)體而 Vec<T> 是結(jié)構(gòu)體總位于索引 0 的項(xiàng)。接著就可以使用 Wrapper 中 Display 的功能了。

此方法的缺點(diǎn)是,因?yàn)?nbsp;Wrapper 是一個(gè)新類型,它沒有定義于其值之上的方法;必須直接在 Wrapper 上實(shí)現(xiàn) Vec<T> 的所有方法,這樣就可以代理到self.0 上 —— 這就允許我們完全像 Vec<T> 那樣對(duì)待 Wrapper。如果希望新類型擁有其內(nèi)部類型的每一個(gè)方法,為封裝類型實(shí)現(xiàn) Deref trait(第十五章 “通過 Deref trait 將智能指針當(dāng)作常規(guī)引用處理” 部分討論過)并返回其內(nèi)部類型是一種解決方案。如果不希望封裝類型擁有所有內(nèi)部類型的方法 —— 比如為了限制封裝類型的行為 —— 則必須只自行實(shí)現(xiàn)所需的方法。

上面便是 newtype 模式如何與 trait 結(jié)合使用的;還有一個(gè)不涉及 trait 的實(shí)用模式?,F(xiàn)在讓我們將話題的焦點(diǎn)轉(zhuǎn)移到一些與 Rust 類型系統(tǒng)交互的高級(jí)方法上來吧。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)