注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。
関連型は、Rust型システムの強力な部分です。関連型は、「型族」という概念と関連が有り、
言い換えると、複数の型をグループ化するものです。
この説明はすこし抽象的なので、実際の例を見ていきましょう。
例えば、 Graph
トレイトを定義したいとしましょう、このときジェネリックになる2つの型: 頂点の型、辺の型 が存在します。
そのため、以下のように Graph<N, E>
と書きたくなるでしょう:
trait Graph<N, E> { fn has_edge(&self, &N, &N) -> bool; fn edges(&self, &N) -> Vec<E>; // etc }
たしかに上のようなコードは動作しますが、この Graph
の定義は少し扱いづらいです。
たとえば、任意の Graph
を引数に取る関数は、 同時に 頂点 N
と辺 E
についてもジェネリックとなることになります:
fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 { ... }
この距離を計算する関数distanceは、辺の型に関わらず動作します、そのためシグネチャに含まれる E
に関連する部分は邪魔でしかありません。
本当に表現したいことは、それぞれのグラフ( Graph
)は辺( E
)や頂点( N
)で構成されているということです。
それは、以下のように関連型を用いて表現することができます:
trait Graph { type N; type E; fn has_edge(&self, &Self::N, &Self::N) -> bool; fn edges(&self, &Self::N) -> Vec<Self::E>; // etc }
このようにすると、Graph
を使った関数は以下のように書くことができます:
fn distance<G: Graph>(graph: &G, start: &G::N, end: &G::N) -> u32 { ... }
もう E
について扱う必要はありません!
もっと詳しく見ていきましょう。
早速、Graph
トレイトを定義しましょう。以下がその定義です:
trait Graph { type N; type E; fn has_edge(&self, &Self::N, &Self::N) -> bool; fn edges(&self, &Self::N) -> Vec<Self::E>; }
非常にシンプルですね。関連型には type
キーワードを使い、そしてトレイトの本体や関数で利用します。
これらの type
宣言は、関数で利用できるものと同じものが全て利用できます。
たとえば、 N
型が Display
を実装していて欲しい時、つまり私達が頂点を出力したい時、以下のようにして指定することができます:
use std::fmt; trait Graph { type N: fmt::Display; type E; fn has_edge(&self, &Self::N, &Self::N) -> bool; fn edges(&self, &Self::N) -> Vec<Self::E>; }
通常のトレイトと同様に、関連型を使っているトレイトは実装するために impl
を利用します。
以下は、シンプルなGraphの実装例です:
struct Node; struct Edge; struct MyGraph; impl Graph for MyGraph { type N = Node; type E = Edge; fn has_edge(&self, n1: &Node, n2: &Node) -> bool { true } fn edges(&self, n: &Node) -> Vec<Edge> { Vec::new() } }
この奇妙な実装は、つねに true
と空の Vec<Edge>
を返しますが、どのように定義したら良いかのアイデアをくれます。
まず、はじめに3つの struct
が必要です、ひとつはグラフのため、そしてひとつは頂点のため、そしてもうひとつは辺のため。
もし異なる型を利用することが適切ならば、そのようにすると良いでしょう、今回はこの3つの struct
を用います。
次は impl
の行です、これは他のトレイトを実装するときと同様です。
そして、=
を関連型を定義するために利用します。
トレイトが利用する名前は =
の左側にある名前で、実装に用いる具体的な型は右側にあるものになります。
最後に、具体的な型を関数の宣言に利用します。
すこし触れておきたい構文: トレイトオブジェクト が有ります。 もし、トレイトオブジェクトを以下のように関連型から作成しようとした場合:
fn main() { trait Graph { type N; type E; fn has_edge(&self, &Self::N, &Self::N) -> bool; fn edges(&self, &Self::N) -> Vec<Self::E>; } struct Node; struct Edge; struct MyGraph; impl Graph for MyGraph { type N = Node; type E = Edge; fn has_edge(&self, n1: &Node, n2: &Node) -> bool { true } fn edges(&self, n: &Node) -> Vec<Edge> { Vec::new() } } let graph = MyGraph; let obj = Box::new(graph) as Box<Graph>; }let graph = MyGraph; let obj = Box::new(graph) as Box<Graph>;
以下の様なエラーが発生します:
error: the value of the associated type `E` (from the trait `main::Graph`) must
be specified [E0191]
let obj = Box::new(graph) as Box<Graph>;
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24:44 error: the value of the associated type `N` (from the trait
`main::Graph`) must be specified [E0191]
let obj = Box::new(graph) as Box<Graph>;
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
上のようにしてトレイトオブジェクトを作ることはできません、なぜなら関連型について知らないからです 代わりに以下のように書くことができます:
fn main() { trait Graph { type N; type E; fn has_edge(&self, &Self::N, &Self::N) -> bool; fn edges(&self, &Self::N) -> Vec<Self::E>; } struct Node; struct Edge; struct MyGraph; impl Graph for MyGraph { type N = Node; type E = Edge; fn has_edge(&self, n1: &Node, n2: &Node) -> bool { true } fn edges(&self, n: &Node) -> Vec<Edge> { Vec::new() } } let graph = MyGraph; let obj = Box::new(graph) as Box<Graph<N=Node, E=Edge>>; }let graph = MyGraph; let obj = Box::new(graph) as Box<Graph<N=Node, E=Edge>>;
N=Node
構文を用いて型パラメータ N
にたいして具体的な型 Node
を指定することができます、E=Edge
についても同様です。
もしこの制約を指定しなかった場合、このトレイトオブジェクトに対してどの impl
がマッチするのか定まりません。