Progrust Library.

// dictionary

関連型.

関連型は、トレイトの中にtype Item;のように型のプレースホルダを宣言しておき、実際の型はimpl側でtype Item = u32;と決める仕組みです。メソッドや関連関数と同じくトレイトが持つ項目の1つで、実装した型に関連付けられた型エイリアスという位置づけです1。トレイトの中ではSelf::Item、外からは型引数を通してT::Itemのように参照します2。

use std::fmt::Debug;

// 「おすすめを1つ返せる」という振る舞い。返す型は実装側で決める
trait Shop {
    type Product; // 関連型(この時点では型を決めない)
    fn recommend(&self) -> Self::Product;
}

#[derive(Debug)]
struct Book {
    title: String,
    price: u32,
}

struct Bookstore;
struct Bakery;

impl Shop for Bookstore {
    type Product = Book; // この実装での Product は Book
    fn recommend(&self) -> Book {
        Book { title: String::from("Rust入門"), price: 3200 }
    }
}

impl Shop for Bakery {
    type Product = String; // こちらの実装では String
    fn recommend(&self) -> String {
        String::from("あんぱん")
    }
}

// 使う側は Shop とだけ書けばよく、商品の型は T::Product で取り出せる
fn show<T: Shop>(shop: &T)
where
    T::Product: Debug,
{
    println!("おすすめ: {:?}", shop.recommend());
}

fn main() {
    show(&Bookstore); // おすすめ: Book { title: "Rust入門", price: 3200 }
    show(&Bakery); // おすすめ: "あんぱん"
}
Playgroundで開く

ジェネリックな型引数との違い

同じ「型を後から決める」仕組みでも、ジェネリクスの型引数とは誰が型を決めるかが違います。トレイト自体が型引数を持つtrait Shop<P>の形なら、Bookstoreに対してShop<Book>とShop<String>の両方を実装できます。一方、関連型は実装1つにつき型が1つに定まるため、impl Shop for Bookstoreは1回しか書けず、Productが何になるかもそこで確定します3。

観点 型引数(trait Shop<P>) 関連型(type Product)
型を決めるのは 使う側 実装側
同じ型への実装 型引数を変えて何度でも書ける 1回だけ
使う側のトレイト境界 T: Shop<Book>と毎回型を書く T: Shopだけでよい
型の取り出し できない(自分で書いた型を使う) T::Productで取り出せる

「1つの型に対する実装は1通りに決めたい」なら関連型、「同じ型に複数の組み合わせを実装したい」なら型引数、という使い分けになります。変換トレイトのFrom<T>のように、u32がFrom<u8>・From<u16>と何通りも実装するトレイトは後者です。

Iterator::Item

標準ライブラリで関連型の代表例となるのがIteratorのItemです。「何を取り出すか」は実装ごとに決まるため、トレイト側では型を固定せず、nextが返すOption型の中身をSelf::Itemと書いています4。

// std::iter::Iterator の宣言(抜粋)
trait Iterator {
    type Item; // 取り出される要素の型
    fn next(&mut self) -> Option<Self::Item>;
}

型引数Iterator<T>だった場合、使う側はnextを呼ぶたびに「どのTのIteratorか」を書き分ける必要がありました3。関連型なので、要素の型を絞りたいときだけIterator<Item = u32>のように=で指定します(このItem = u32という書き方を等価制約と呼びます5)。

// 要素が u32 のものだけを受け取る
fn total<I: Iterator<Item = u32>>(prices: I) -> u32 {
    let mut sum = 0;
    for price in prices {
        sum += price;
    }
    sum
}

fn main() {
    let prices = vec![120u32, 350, 480];
    println!("合計{}円", total(prices.into_iter()));
}
Playgroundで開く

補足

関連型に境界を付ける・デフォルトは書けない

宣言側でtype Product: Debug;のように境界を付けると、実装が指定する型はそれを満たす必要があります6。また関連型には暗黙のSized境界があり、?Sizedで緩められます6。

一方、安定版のRustでは関連型にデフォルトの型は書けません。型を指定できるのは実装側だけです6(デフォルト実装の補足も参照)。同じく、impl 型名 { ... }のimplブロック(固有実装)に関連型を置くこともできません6。

トレイトオブジェクトでは関連型の指定が必須

トレイトオブジェクトは関連型をすべて指定しないと作れません。dyn Iteratorとだけ書くとエラー: E0191になるため、dyn Iterator<Item = u32>のように書きます7。実行時に型が切り替わる以上、要素の型まで曖昧では扱えないためです。

なお、トレイト内のメソッドの戻り値に書いたimpl Traitは、匿名の関連型への糖衣構文として扱われます8。この関連型には名前がないためdyn側から指定できず、そうしたメソッドを持つトレイトはトレイトオブジェクトにできません9。

具体的な型から関連型を取り出す

T::Productという短縮形が使えるのは、Tが型引数のときだけです2。Bookstoreのような具体的な型から取り出すときは、<Bookstore as Shop>::Productと完全修飾構文で書きます。型引数が複数のトレイトを実装していてT::Productでは曖昧な場合も、同じ構文でどのトレイトの関連型かを明示します。

Footnotes

  1. The Rust Reference - Associated Items “Associated types are type aliases associated with another type.” ↩

  2. The Rust Reference - Associated Types “If a type Item has an associated type Assoc from a trait Trait, then <Item as Trait>::Assoc is a type that is an alias of the type specified in the associated type definition. Furthermore, if Item is a type parameter, then Item::Assoc can be used in type parameters.” ↩ ↩2

  3. The Rust Programming Language - Defining Traits with Associated Types “when a trait has a generic parameter, it can be implemented for a type multiple times, changing the concrete types of the generic type parameters each time.” / “With associated types, we don’t need to annotate types, because we can’t implement a trait on a type multiple times.” ↩ ↩2

  4. std::iter::Iterator “type Item … The type of the elements being iterated over.” / “fn next(&mut self) -> OptionSelf::Item” ↩

  5. The Rust Reference - Paths: Generic arguments “The order of generic arguments is restricted to lifetime arguments, then type arguments, then const arguments, then equality constraints.” ↩

  6. The Rust Reference - Associated Types “The optional trait bounds must be fulfilled by the implementations of the type alias.” / “There is an implicit Sized bound on associated types that can be relaxed using the special ?Sized bound.” / “Associated types cannot be defined in inherent implementations nor can they be given a default implementation in traits.” ↩ ↩2 ↩3 ↩4

  7. Error code E0191 “An associated type wasn’t specified for a trait object.” / “Trait objects need to have all associated types specified.” ↩

  8. The Rust Reference - Return-position impl Trait in traits and trait implementations “Every impl Trait in the return type of an associated function in a trait is desugared to an anonymous associated type.” ↩

  9. The Rust Reference - Dyn compatibility “Dispatchable functions must: … Not have an opaque return type” ↩

この辞書が使われているページ

backlinks 9

  1. 辞書impl Trait
  2. 辞書ジェネリクス
  3. 辞書デフォルト実装
  4. 辞書トレイト
  5. 辞書トレイトオブジェクト
  6. 辞書トレイト境界
  7. 辞書演算子オーバーロード
  8. 辞書変換トレイト
  9. 本動かして学ぶRustプログラミング問題集 › 第21章 トレイトオブジェクトと関連型