1.16.1. 讓 Trait 泛型的兩種方式

Trait 能以兩種方式泛型化:

  • 泛型型別參數。範例:trait Foo<T>
  • 關聯型別。範例:trait Foo { type Bar; }

兩者的差異在於:

  • 使用關聯型別時,一個給定型別的 Trait 只有一種實作
  • 使用泛型參數時,則可以有多種實作

簡單建議:如果可行,請優先使用關聯型別。

1.16.2. 泛型(型別參數)Trait

泛型 Trait 需要你指定所有泛型型別參數,並重複撰寫它們的邊界

這在維護上較為困難。例如,若你為一個 Trait 新增泛型型別參數,則所有實作該 Trait 的型別都必須更新其程式碼。

這種形式也可能導致一個 Trait 對某個給定型別有多個實作。此時編譯器在推斷你實際想要哪個 Trait 實例時會遇到困難。有時你必須呼叫如 FromIterator::<u32>::from_iter 這類解決歧義的函式。

在某些情況下,這項功能也是一種優勢,例如:

  • impl PartialEq<BookFormat> for Book,其中 BookFormat 可以是不同型別
  • 你可以同時實作 FromIterator<T>FromIterator<&T> where T: Clone

1.16.3. 關聯型別 Trait

讓我們用一段程式碼作為範例:

trait Contains {
    type A;
    type B;

    // Updates syntax to refer to these new types generically
    fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
}

Enter fullscreen mode Exit fullscreen mode

使用關聯型別時:

  • 編譯器只需要知道實作該 Trait 的型別
  • 邊界可以完全放在 Trait 本身,而不需要重複撰寫
  • 未來新增另一個關聯型別不會影響使用者
  • 具體型別會決定 Trait 內部的關聯型別,因此不需要使用解決歧義的函式。請看這個範例:
impl Contains for Container {
    // Specify what types `A` and `B` are. If the `input` type
    // is `Container(i32, i32)`, the `output` types are determined
    // as `i32` and `i32`.
    type A = i32;
    type B = i32;

    // `&Self::A` and `&Self::B` are also valid here.
    fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
        (&self.0 == number_1) && (&self.1 == number_2)
    }

    // Grab the first number.
    fn first(&self) -> i32 { self.0 }

    // Grab the last number.
    fn last(&self) -> i32 { self.1 }
}

Enter fullscreen mode Exit fullscreen mode

  • 這個範例為 Container 型別實作了 Contains Trait
  • ContainerContains 實作中的關聯型別,是由 type A = i32;type B = i32; 所決定的

無法為多個目標型別實作 Deref

請看 Deref Trait 的原始碼:

pub trait Deref {
    type Target: ?Sized;

    fn deref(&self) -> &Self::Target;
}

Enter fullscreen mode Exit fullscreen mode

  • Targettype Target: ?Sized; 中,就是我們所討論的目標型別

讓我們撰寫一個 Deref 實作來說明:

use std::ops::Deref;

struct Wrapper {
    value: String,
}

impl Deref for Wrapper {
    type Target = String;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

Enter fullscreen mode Exit fullscreen mode

在上面的程式碼中,Wrapper 只能被解參考為 String。但如果你想讓 Wrapper 同時被解參考為 Stringstr,Rust 並允許你再次實作 Deref,因為 Target 只能有一個具體型別。

換句話說,這段程式碼是非法的:

use std::ops::Deref;

struct Wrapper {
    value: String,
}

// First Deref implementation, Target = String
impl Deref for Wrapper {
    type Target = String;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

// This is illegal: Rust does not allow a second `Deref` implementation
// for the same `Wrapper` type.
impl Deref for Wrapper {
    type Target = str;  // Conflict: Rust cannot infer which `Target` applies

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

Enter fullscreen mode Exit fullscreen mode


無法使用多個 Item 來實作 Iterator Trait

原因與無法為多個目標型別實作 Deref 相同:這主要涉及關聯型別的唯一性以及Rust 編譯器的推斷規則