関連型

関連型は、Rust型システムの強力な部分です。関連型は、「型族」という概念と関連が有り、 言い換えると、複数の型をグループ化するものです。 この説明はすこし抽象的なので、実際の例を見ていきましょう。 例えば、 Graph トレイトを定義したいとしましょう、このときジェネリックになる2つの型: 頂点の型、辺の型 が存在します。 そのため、以下のように Graph<N, E> と書きたくなるでしょう:

fn main() { trait Graph<N, E> { fn has_edge(&self, &N, &N) -> bool; fn edges(&self, &N) -> Vec<E>; // etc } }
trait Graph<N, E> {
    fn has_edge(&self, &N, &N) -> bool;
    fn edges(&self, &N) -> Vec<E>;
    // etc
}

たしかに上のようなコードは動作しますが、この Graph の定義は少し扱いづらいです。 たとえば、任意の Graph を引数に取る関数は、 同時に 頂点 N と辺 E についてもジェネリックとなることになります:

fn main() { fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 { ... } }
fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 { ... }

この距離を計算する関数distanceは、辺の型に関わらず動作します、そのためシグネチャに含まれる E に関連する部分は邪魔でしかありません。

本当に表現したいことは、それぞれのグラフ( Graph )は辺( E )や頂点( N )で構成されているということです。 それは、以下のように関連型を用いて表現することができます:

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>; // etc } }
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 main() { fn distance<G: Graph>(graph: &G, start: &G::N, end: &G::N) -> u32 { ... } }
fn distance<G: Graph>(graph: &G, start: &G::N, end: &G::N) -> u32 { ... }

もう E について扱う必要はありません!

もっと詳しく見ていきましょう。

関連型を定義する

早速、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>; } }
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 を実装していて欲しい時、つまり私達が頂点を出力したい時、以下のようにして指定することができます:

fn main() { 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>; } }
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の実装例です:

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() } } }
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 がマッチするのか定まりません。