関連型 (Associated types) はトレイトのアイテムとして定義される。そして、トレイトを実装する型から、その関連型を通して、別の型を得られるようにする。つまり、トレイトの関連型 A は「トレイトの実装型 X から、別の型 A(X) への写像」となる。
トレイトの側では、type Assoc;
のように定義する。
トレイトの実装側では、type Assoc = SomeType;
のように型を指定する。
以下では、トレイト Sequence
からの生成値のために、関連型 Value
を定義している。
そして、Sequence
を実装する型 IntCounter
にて、Value
は i32
に指定されている。
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 (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) を急いだ結果らしく、将来的には改善される見込みらしい。