関連型 (Associated types) はトレイトのアイテムとして定義される。そして、トレイトを実装する型から、その関連型を通して、別の型を得られるようにする。つまり、トレイトの関連型 A は「トレイトの実装型 X から、別の型 A(X) への写像」となる。

基本

トレイトの側では、type Assoc; のように定義する。

トレイトの実装側では、type Assoc = SomeType; のように型を指定する。

サンプル

以下では、トレイト Sequence からの生成値のために、関連型 Value を定義している。

そして、Sequence を実装する型 IntCounter にて、Valuei32 に指定されている。


fn main() {
    let seq = &mut IntCounter(0);
    let seq = seq as &mut dyn Sequence<Value = i32>;
    assert_eq!(seq.next(), 1);
}

trait Sequence {
    type Value;
    fn next(&mut self) -> Self::Value;
}

struct IntCounter(i32);
impl Sequence for IntCounter {
    type Value = i32;
    fn next(&mut self) -> Self::Value {
        self.0 += 1;
        self.0
    }
}

境界の指定

関連型に境界を指定する方法について。

定義時の指定

type Assoc: Bounds のように定義すると、境界が付加される。

これにより、実装側で Assoc として指定できる型に Bounds の制約がつく。

サンプル

以下のコードは、直前の例と似たような構成だが、関連型 Value に境界 Ord がある。

そして、型 IntCounter における Value である i32 はもちろん Ord である。


fn main() {
    let seq = &mut IntCounter(0);
    skip_to(seq, 3);
    assert_eq!(seq.next(), 4);
}

trait Sequence {
    type Value: Ord;
    fn next(&mut self) -> Self::Value;
}

struct IntCounter(i32);
impl Sequence for IntCounter {
    type Value = i32;
    fn next(&mut self) -> Self::Value {
        self.0 += 1;
        self.0
    }
}

fn skip_to<T>(target: &mut T, max: T::Value)
where
    T: Sequence
{
    loop {
        let val = target.next();
        if val >= max {
            return
        }
    }
}

使用時の指定

定義時に指定した境界の他に、関連型の使用時にも境界を追加できる。

これには、型パラメタ T が関連型 Assoc を持つ場合、T::Assoc: Bounds のようにする。

サンプル

以下のコードは、直前の例と同じ動作をする。ただし、関連型 Value の境界 Ord は、関連型の定義時でなく、外部の関数 skip_to での関連型の使用時に指定されている。


fn main() {
    let seq = &mut IntCounter(0);
    skip_to(seq, 3);
    assert_eq!(seq.next(), 4);
}

trait Sequence {
    type Value;
    fn next(&mut self) -> Self::Value;
}

struct IntCounter(i32);
impl Sequence for IntCounter {
    type Value = i32;
    fn next(&mut self) -> Self::Value {
        self.0 += 1;
        self.0
    }
}

fn skip_to<T>(target: &mut T, max: T::Value)
where
    T: Sequence,
    T::Value: Ord,
{
    loop {
        let val = target.next();
        if val >= max {
            return
        }
    }
}

ジェネリクス (GAT)

関連型はジェネリクスパラメタを付加する事で、パラメトリックにできる。

この機能は GAT (Generic Associated Types) と呼ばれている。

where による境界の指定

GAT への境界の指定はコロンによる方式の他に、where による方式にも対応している。

これにより、GAT のパラメタへの境界の指定もできるようになっている。

型パラメタ

関連型のジェネリクスパラメタに型パラメタを使うパターン。

サンプル

以下では、トレイト CollectionBuilder が生成するコレクションのために、ジェネリックな関連型 Output<T> が定義されている (T はコレクションの要素型)。


fn main() {
    let vec_builder = VecBuilder();
    let vec = vec_builder.build(42, 3);
    assert_eq!(vec, vec![42, 42, 42]);
}

trait CollectionBuilder {
    type Output<T>: Default + Collection<T>;
    fn build<T: Clone>(&self, v: T, n: usize) -> Self::Output<T> {
        let mut result = <Self::Output<T>>::default();
        for _ in 0..n {
            result.add(v.clone());
        }
        result
    }
}

trait Collection<T> {
    fn add(&mut self, x: T);
}

struct VecBuilder();
impl CollectionBuilder for VecBuilder {
    type Output<T> = Vec<T>;
}

impl<T> Collection<T> for Vec<T> {
    fn add(&mut self, x: T) {
        self.push(x);
    }
}

ライフタイム

関連型のジェネリクスパラメタにライフタイム指定子を使うパターン。

サンプル

以下では、トレイト IntCollection が生成するイテレータのために、ジェネリックな関連型 IntStream<'a> が定義されている。ここで、'a はイテレータのアイテムが所属するライフタイムで、IntCollection'a から参照できなければならない (Self: 'a の境界のため)。


use std::slice::Iter;

fn main() {
    let vec = vec![1, 2, 3];
    let stream = vec.int_stream();
    assert_eq!(stream.sum::<i32>(), 6);
}

trait IntCollection {
    type IntStream<'a>: Iterator<Item = &'a i32> where Self: 'a;
    fn int_stream(&self) -> Self::IntStream<'_>;
}

impl IntCollection for Vec<i32> {
    type IntStream<'a> = Iter<'a, i32> where Self: 'a;
    fn int_stream(&self) -> Self::IntStream<'_> {
        self.iter()
    }
}

制限

、GAT にはまだ制限が多い。この事は公式ブログでも紹介されている。それによると、これは導入 (Rust 1.65: 2022/11/03) を急いだ結果らしく、将来的には改善される見込みらしい。