1.16.1. トレイトをジェネリックにする2つの方法

トレイトは2つの方法でジェネリックにできます:

  • ジェネリック型パラメータ。例:trait Foo<T>
  • 関連型。例:trait Foo { type Bar; }

2つの違いは以下の通りです:

  • 関連型を使用する場合、特定の型に対するトレイトは1つの実装のみを持つ
  • ジェネリックパラメータを使用する場合、複数の実装が可能

簡単な提案:可能であれば、関連型を優先してください。

1.16.2. ジェネリック (型パラメータ) トレイト

ジェネリックトレイトでは、すべてのジェネリック型パラメータを指定し、その境界を繰り返す必要があります。

これはメンテナンスがやや難しくなります。たとえば、トレイトにジェネリック型パラメータを追加した場合、そのトレイトの実装者はすべてコードを更新する必要があります。

この形式では、ある型に対してトレイトが複数の実装を持つ問題も発生する可能性があります。その場合、コンパイラは実際にどのトレイトのインスタンスを必要としているかを推論するのが難しくなります。時にはFromIterator::<u32>::from_iterのような曖昧さ解決関数を呼び出す必要があります。

場合によっては、この機能は利点にもなります。例えば:

  • impl PartialEq<BookFormat> for Book のように、BookFormat が異なる型である場合
  • FromIterator<T>FromIterator<&T> where T: Clone の両方を実装できる

1.16.3. 関連型トレイト

コード例を使って説明しましょう:

trait Contains {
    type A;
    type B;

    // これらの新しい型をジェネリックに参照する構文を更新
    fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
}

全画面表示にする 全画面表示を終了する

関連型を使用する場合:

  • コンパイラはトレイトを実装する型のみを知る必要がある
  • 境界はトレイト自体に完全に存在でき、繰り返す必要がない
  • 将来的に関連型を追加しても、ユーザーに影響を与えない
  • 具体的な型がトレイト内の関連型を決定するため、曖昧さ解決関数を使用する必要がない。次の例を見てください:
impl Contains for Container {
    // `A` と `B` が何の型かを指定する。`input` 型が
    // `Container(i32, i32)` の場合、`output` 型は `i32` と `i32` に決定される
    type A = i32;
    type B = i32;

    // ここでも `&Self::A` と `&Self::B` が有効
    fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
        (&self.0 == number_1) && (&self.1 == number_2)
    }

    // 最初の数値を取得
    fn first(&self) -> i32 { self.0 }

    // 最後の数値を取得
    fn last(&self) -> i32 { self.1 }
}

全画面表示にする 全画面表示を終了する

  • この例では、Container 型に対して Contains トレイトを実装している
  • ContainerContains 実装における関連型は、type A = i32;type B = i32; によって決定される

Deref を複数のターゲット型に対して実装できない

Deref トレイトのソースコードを見てみましょう:

pub trait Deref {
    type Target: ?Sized;

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

全画面表示にする 全画面表示を終了する

  • type Target: ?Sized;Target が、ここで話しているターゲット型です

例示のために Deref の実装を書いてみましょう:

use std::ops::Deref;

struct Wrapper {
    value: String,
}

impl Deref for Wrapper {
    type Target = String;

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

全画面表示にする 全画面表示を終了する

上のコードでは、WrapperString としてのみデリファレンスできます。しかし、WrapperStringstr両方同時にデリファレンスしたい場合、Rust は Deref を再度実装することを許可しません。なぜなら、Target は1つの具体的な型しか持てないからです。

言い換えると、これは不正です:

use std::ops::Deref;

struct Wrapper {
    value: String,
}

// 最初の Deref 実装、Target = String
impl Deref for Wrapper {
    type Target = String;

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

// これは不正:Rust は同じ `Wrapper` 型に対して2つ目の `Deref` 実装を許可しない
impl Deref for Wrapper {
    type Target = str;  // 衝突:Rust はどの `Target` が適用されるかを推論できない

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

全画面表示にする 全画面表示を終了する


複数の Item を使用して Iterator トレイトを実装できない

理由は、複数のターゲット型に対して Deref を実装できない理由と同じです:主に関連型の唯一性Rust コンパイラの推論規則が関係しています。