Rust の関連型はジェネリクスではだめなのか？

これは関連型に対する自分の理解をまとめたものです。[高度なトレイト - The Rust Programming Language](https://doc.rust-jp.rs/book/second-edition/ch19-03-advanced-traits.html)の関連型の項を補足する形でもあります。これは変だ、またはもっと正確な認識がある、という場合はぜひぜひ教えていただけると嬉しいです。コードは少し古いですが `rustc 1.34.0-nightly (097c04cf4 2019-02-24)` により確認しています。

## トレイトにおけるジェネリクス・関連型とは

Rust のトレイトは、トレイトにおいて型を抽象化するためにジェネリクスや関連型を用いることができます。まずはこれらについて説明します。まだ本題でないので、知っている方は読み飛ばしていただいて大丈夫です。

### ジェネリクスの例

例えば、標準ライブラリの `std::convert::From` トレイトはある型を別の型の値から生成できることを表すトレイトですが、概ね次のように定義されています。

```rust
pub trait From<T> {
    fn from(from: T) -> Self;
}
```

この定義の `<T>` となっているところがジェネリクスで、 `T` という名前の型変数を宣言しています。

このトレイトを使うと、たとえば型 `u8` の値を型 `i32` に変換できることを表すには `impl From<u8> for i32` とできます。また型 `U` に対して `U: From<T>` という制約が与えられた場合、型 `U` は型 `T` から変換できることを表します。

### 関連型の例

例えば、標準ライブラリの `std::iter::Iterator` トレイトは `next()` メソッドによってある型の値を順々に取り出せることを表すトレイトですが、デフォルト実装されるメソッドやアノテーションなどを除くと、核になるのは次の部分です。

```rust
pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}
```

この定義の `type Item;` となっているところが関連型で、 `Item` という名前の型を宣言しています。

このトレイトを使うと、たとえば型 `CharIter` が型 `char` の値を生成するイテレータであることを表すには

```rust
impl Iterator for CharIter {
    type Item = char;
    // ...
}
```

とします。また型 `U` に対して `U: Iterator<Item = T>` という制約が与えられた場合、型 `U` は型 `T` の値を生成するイテレータであることを表します。

## 本題 : 関連型はジェネリクスではだめなのか？

先の `std::iter::Iterator` の例を見ていると、関連型を使わなくても

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

のように、ジェネリクスを使って `Item` を実装すればいい気がしますが、どうなのでしょうか？

結論から言うと、 **この例では、** そのようにトレイトを定義しても使えないことはないです。ただしジェネリクスと関連型は意味が違うので、いくつかの面倒と問題を引き起こします。ざっくり言うと次のような違いです。

※ これでない例で、そもそも **関連型が必要になる例** というのもあります[^1]。ただし今回はそこには踏み込みません。

- ジェネリクスは「トレイト」と「型変数がとりえる型」が 1 対多
- 関連型は「トレイト」と「関連型がとりえる型」が 1 対 1

ジェネリクスでは、 `Iterator<u8>` と `Iterator<char>` は別のトレイトなので、両方を同じ型 `T` に対して実装することもできてしまいます。すると `T` に実装されている `Iterator<Item>` の `Item` は `u8` と `char` になります。これが 1 対 **多** です。一方、関連型では、トレイトとしてはあくまで `Iterator` というトレイトなので、これは同じ型 `T` に対しては一回しか実装できません。その一回の実装の中で `Item` に型を結び付けるため、やはり `Item` は一つの型でしかありえません。これが 1 対 **1** です。

従って、関連型を使うべき場合とは「ある型に対して別の型が一意に定まる場合」といえます。`Iterator` などはまさにそうです。型 `Vec<u8>` をイテレートすると型 `u8` の要素が出てくるのが自然でしょう。普通 `String` や `i32` は出てきません。 `String` のようにバイト列 `[u8]` か文字列 `[char]` か曖昧なコンテナは `String::bytes()` や `String::chars()` を一旦経由しないとイテレータに変換できないようになっています。いずれにせよ、ある `Iterator` が存在したとき、そのイテレータが生成できる型が複数あるというのは不自然ですよね。

逆にジェネリクスを使うべき場合とは「ある型に対して別の型がいくつでも有り得る場合」と言えます。`From` などはまさにそうです。「型 `i64` に失敗なく変換できる型」は決して一つとは限らず、 `i32` や `u32` や `u8` や... といくらでも考えられます。 `From` の定義に関連型を使ってしまうと「変換元になる型は何か一つしかない」ことを示すことになります。

では、一意に定まる場合にジェネリクスを使うと何が問題になるかを見ていきましょう。

## 説明に使うトレイト

「コンテナ型の値から先頭の要素をとりだすことができる」ことを表すトレイト、 `GetFirst` というものを考えることにします。コンテナ型というのはどのような型の要素を持っているかわかりませんから、ジェネリクスか関連型を使って要素の型を抽象化する必要があります。この場合「コンテナ型の要素の型」というのは `Vec<u8>` なら `u8` 、 `String` なら `char` 、 `(i32, i32)` なら `i32` というふうにコンテナ型に対応して一通りに定まりますから、ここでは関連型を使うのが正しいです。しかしあえてジェネリクスにしてみましょう。

なお、 `String` は `char` のコンテナとも `u8` のコンテナとも言えて曖昧ではありますが、用途に合わせてコンテナ型に対する解釈を定めれば一通りになります。文字の列ともバイトの列とも言えるかもしれませんが、文字ともバイトとも言えるものの列ではないですよね。

```rust
trait GetFirst<Item> {
    fn first(&self) -> Option<Item>;
}
```

## 問題 1 : 型推論が効かなくなるとメソッド記法が使えなくなる

ではこのトレイトを仮に `String` と `Vec<u8>` と `(i32, i32)` に実装してみましょう。

```rust
// String を char のコンテナと見て一文字目を返す。
// 返す型は char
impl GetFirst<char> for String {
    fn first(&self) -> Option<char> {
        self.chars().next()
    }
}

// Vec<u8> は言わずもがな u8 のコンテナで、一つ目の要素を返す。
// 返す型は u8
impl GetFirst<u8> for Vec<u8> {
    fn first(&self) -> Option<u8> {
        self.iter().cloned().next()
    }
}

// (i32, i32) を二要素の i32 のコンテナと見て左側を返す。
// 返す型は i32
impl GetFirst<i32> for (i32, i32) {
    fn first(&self) -> Option<i32> {
        Some(self.0)
    }
}
```

使ってみます:

```rust
fn main() {
    let s = String::from("hello");
    println!("{:?}", s.first()); // => Some('h')

    let v = vec![0u8, 1u8, 2u8];
    println!("{:?}", v.first()); // => Some(0)

    let t = (1i32, 2i32);
    println!("{:?}", t.first()); // => Some(1)
}
```

問題なく実行できますね。しかし、ここにうっかり次のような実装を追加してしまうと問題が見えます。

```rust
// String をバイト列のコンテナと見て一バイト目を返す。
// 返す型は u8
impl GetFirst<u8> for String {
    fn first(&self) -> Option<u8> {
        self.bytes().next()
    }
}
```

うっかりこのような実装を追加してしまうと、 **使用箇所で** エラーになってしまいます。

```
error[E0282]: type annotations needed
  --> junk01-19-09.rs:39:24
   |
39 |     println!("{:?}", s.first()); // => Some('h')
   |                        ^^^^^ cannot infer type for `Item`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0282`.
```

コンパイラが言っていることは至極当然で、 `String` には `GetFirst<char>` と `GetFirst<u8>` の両方が実装されているため、型変数 `Item` の型を推論できないと言っています。これらを呼びわけるためには戻り値型から推論させるか、メソッド記法を諦めて書き下すしかありません。

```rust
println!("{:?}", GetFirst::<char>::first(&s)); // => Some('h')
println!("{:?}", GetFirst::<u8>::first(&s)); // => Some(104)
```

先ほど `s.first()` だけで正しくコンパイルできたのは、型 `String` に対して `GetFirst` は `GetItem<char>` しか実装されていなかったために、自動的に `Item = char` であると **型推論されたから** なのです。あくまでも推論に依存しているので、このように複数の候補がある場合など推論できない状況に陥るとエラーになってしまいます。

今回の例では、コンテナが「持つ」とされる要素の型は本来ただ一つであるはずなのに `String` が `char` を持つとも `u8` を持つともしてしまったためにエラーが起きました。問題はこれが **使用時にエラーになった** ということで、型に両方を実装しただけではエラーになりません。それはジェネリクスを使うのが適正なことだってあるから当然です。しかし今回のような場合はそれでは困るので、型引数 `Item` が異なる `GetFirst<Item>` を同じ型に対して実装しないように「気をつける」ことでしか対応できません。

また、 `GetFirst<Item>` がライブラリで定義されているトレイトだったりすると、その用途を誤解したライブラリのユーザーが、同じ型に複数の `GetFirst<Item>` を実装した上でそれをライブラリとして公開するかもしれません。そうなると本来 `GetFirst<Item>` が実現したかった意図と実際の使われ方がかけはなれていくこともありえます。

もし `Item` が関連型になっていれば、この `Item` の定義は `GetFirst` を何か型に実装するときになされることとなります。 `GetFirst` はジェネリクスではありませんから真に単一のトレイトです。すると `GetFirst` を `String` に実装できる機会は一度だけですから、 `Item` も必ず一通りにしかなりません。この型は型により一意に定まるものだという意図も明確にできます。

## 問題 2 : 他のジェネリクスで制約を設定したいときに型変数が増える

これは実質面倒なだけではありますが、 `GetFirst` を実装した型を関数で受け取りたいときに余計な型変数が増えます。それはもちろんそうで、ジェネリクスのシステム的には `GetFirst<u8>` と `GetFirst<char>` の両方があるかもしれないので、どちらに対しても呼び出せるよう、ジェネリクスにする必要があります。

```rust
fn get_first_of<Item, U: GetFirst<Item>>(x: &U) -> Option<Item> {
    x.first()
}
```

もし `Item` が関連型になっていれば、 `U: GetFirst` のみですみます。 `U` に `GetFirst` を実装した時点で `Item` の定義がなされており、必ず一つに定まっているからです。ジェネリクスはトレイトごと (`GetFirst<u8>` と `GetFirst<char>` は別のトレイトです) に一つの型を、関連型はそのトレイトを実装する型に一つの型を結び付けるものだと考えてもいいかもしれません。

この程度ならば面倒なくらいですむかもしれませんが、これが増えてくると厄介です。意味のない例ですが、例えば次のようにしなければなりません。

```rust
use std::ops::Add;

trait Foo<T, U>
where
    T: Copy + Eq + Ord,
    U: Copy + Eq + Ord + Add<T, Output = U>,
{
    fn foo(x: T, y: U) -> U;
}

struct X;

impl Foo<i32, i32> for X {
    fn foo(_: i32, _: i32) -> i32 {
        42
    }
}

fn bar<T, U, V>(_: V)
where
    T: Copy + Eq + Ord,
    U: Copy + Eq + Ord + Add<T, Output = U>,
    V: Foo<T, U>,
{
}

fn main() {
    bar(X);
}
```

特に `bar()` に注目してください。このように、とくにジェネリクスの型変数に制約がある場合などは関数ごとに一々制約ごと書き直さなくてはなりません。ジェネリクスを使うべき場合はもちろん制約が必要となるので仕方がないですが、関連型を使うべき場合、つまり型に対して型変数が一通りに定まる場合は、トレイトの実装時に型が定まっているわけなので、関数を呼び出すごとに確認しなくても、そこできちんと制約に則っていることが分かれば本来それでよいはずです。実際、関連型を使えば

```rust
use std::ops::Add;

trait Foo {
    type T: Copy + Eq + Ord;
    type U: Copy + Eq + Ord + Add<Self::T, Output = Self::U>;

    fn foo(x: Self::T, y: Self::U) -> Self::U;
}

struct X;

impl Foo for X {
    type T = i32;
    type U = i32;

    fn foo(_: i32, _: i32) -> i32 {
        42
    }
}

fn bar<V: Foo>(_: V) {}

fn main() {
    bar(X);
}
```

とかけます。特に `bar()` は非常にすっきりしたと思います。

## 参考

- [標準ライブラリのドキュメント](https://doc.rust-lang.org/std/)
- [Rust の関連型の使いどころ | κeen の Happy Hacκing Blog](https://keens.github.io/blog/2016/11/22/rustnokanrenkatanotsukaidokoro/)
- [高度なトレイト - The Rust Programming Language](https://doc.rust-jp.rs/book/second-edition/ch19-03-advanced-traits.html)

[^1]: [Rust の関連型の使いどころ | κeen の Happy Hacκing Blog](https://keens.github.io/blog/2016/11/22/rustnokanrenkatanotsukaidokoro/) も参考になるかと思われます。