Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
19
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

RustのAssociated Typeについて

RustのAssociated Typeについて

by tacke_jp
1 / 16

自己紹介

  • @tacke_jp
  • 株式会社ノハナでエンジニアをしています
  • 今はParse.com終了にサービスが道連れにならないように移行作業をしています
  • 趣味でHaskellとかRustとか触っています
  • 今日はRustを触り始めて興味を持った "Associated Type" についての話をします

Associated Type (関連型) is 何?

  • Traitに紐づく型パラメータ
    • Traitを実装する型に対して一意に定まる
      (型に関連付く型 = "Associated")
  • 他の言語だとHaskellやSwiftなどが対応してる
  • 概念としては Type Family (型族) に近いらしい
    • 詳しい人教えてください😭

グラフを表現するtraitを考える。

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

(公式ドキュメントより)


実装例

struct MyGraph;
struct Node;
struct Edge;

impl Graph<Node, Edge> for MyGraph {
  fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
    true
  }

  fn edges(&self, n: &Node) -> Vec<Edge> {
    Vec::new()
  }
}

これに対してグラフの2点間の距離を計算する関数distanceを定義することを考える。

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

問題点

  • シグネチャの表現が冗長で誤解を招きやすい
    • 任意のNEを取れるかのように見える
    • 実際は impl Graph<> 時に定義されたものしか使えない
  • 型パラメータが増えたときに辛い
    • 例えば <A, B, C, D, E, F, G: Graph<A, B, C, D, E, F>> 😨
    • これをGraphを引数や戻り値として使う関数ごとに書かないといけないとなると...

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

関数distanceを定義してみる

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

すっきり書けた🙆


型消去する場合はこうする。

let graph = MyGraph;
let graphs = Vec::new()
               as Vec<Graph<<N=Node, E=Edge>>;
graphs.add(graph);

(型消去すると NE が一意に定まらないので、具体的に指定してやる必要がある。)


メリット

  • 余分な型パラメータを宣言する必要がなく可読性が高い
  • 型パラメータの追加/削除などの変更に強い
    • シグネチャを書き直さなくても済む
    • ライブラリ作者がAssociated Typeにしておいてくれると使い手が楽できる

注意点

  • 型を代入しなくて良くなる訳ではない
    • 関数のシグネチャで <G: Graph> と型パラメータを省略できるが裏では代入されている
    • let g: Graph という変数を宣言することは出来ない
  • 型の表現力が増すわけではない
    • 同等の表現はGenericsを使っても書ける

=> あくまで型レベルでのプログラミングを楽にするための機能


おまけ

Rustでこう書きたいけど書けない。

trait Graph {
    type N;
    type E;
    fn compare<G: Graph>(&self, other: G)
      -> bool
      where Self.N == G.N
          , Self.E == G.E;
}
  • error: equality constraints are not yet supported in where clauses (#20041) と怒られる😇
    • Genericsならfn compare<G: Graph<N, E>>(&self, other: G) -> bool;こう書ける

ちなみにSwiftだと書ける。そのうち対応されると良いな...


おまけ(その2)

termoshtt さんにコメントをいただきました。
(ありがとうございます!)

traitを使って型レベルでイコールを作るコードを紹介いただきました。

trait Equal<A> {}
impl<A> Equal<A> for A {}

trait Graph {
    type N;
    type E;
    fn compare<G: Graph>(&self, other: G) -> bool
        where Self::N: Equal<G::N>,
              Self::E: Equal<G::E>;
}

おまけ(その3)

blackenedgoldさんにもコメント頂きました。(ありがとうございます!)
Self::N Self::E を代入してしまえば良いんですね...!

trait Graph {
    type N;
    type E;
    fn compare<G: Graph<N = Self::N, E = Self::E>>(&self, other: G)
      -> bool;
}

まとめ

  • Genericsで書いていたところをAssociated Typeで書くと簡潔になる
  • Genericsを使う際の便利な補助機能だと思って使えば良さそう
  • 型かわいいよ型

References

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
19
Help us understand the problem. What are the problem?