注意: 最新版のドキュメントをご覧ください。この第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
がマッチするのか定まりません。