Learning Rust - Associated Types

Syntax and Semantics

Associated Types

いきなりですが、抽象的なグラフに関するtraitを作りたいとします。 例えば以下のように書きますよね。 Nがnode, Eがedgeです。

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

で、Graphを受け取る関数を作ります(Traitに含めてもいいんですが、それはおいといて)。 Node間の距離を計算する関数です。

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

このように、シグネチャにN, Eが何度も登場します。 Graphの型がわかればN, Eもわかるので、これは無駄っぽいですね。

そこで、Graphにnodeとedgeを関連付けます。 このとき使う機能が、’associated type’です。

trait Graph {
    type N;
    type E;

    fn has_edge(&self, &Self::N, &Self::N) -> bool;
    fn edges(&self, &Self::N) -> Vec<Self::E>;
    // etc
}

Syntaxは上のように、trait内にtype Nのように記述します。 さきほどのdistance()関数のシグネチャは次のように変わります。

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

すこしすっきりしましたね。 しかも、不要だったEの記述がなくなっています。

Defining associated types

Associated typesの定義時に、引数の型のように、あるtraitをimplしていることを要求できます。 Syntaxは引数のときと同じです。

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>;
}

Implementing associated types

Associated typesをもっているtraitをimplするときも、これまでとあまり変わりません。

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()
    }
}

Associated typesであるN, Eに具体的な型を指定し、 指定した型をつかってメソッドを定義していきます。

Trait objects with associated types

Associated typesをもつtraitのtrait objectに変換するときは、 associated typesが具体的には何の型なのか指定する必要があります。 どの型に対するimplか分からなくなりますからね。

let graph = MyGraph;
let obj = Box::new(graph) as Box<Graph<N = Node, E = Edge>>;