戻り値でのimpl Trait
例
rustfn f() -> impl Iterator<Item = i32> {
vec![1, 2, 3].into_iter()
}
f
は「 Iterator<Item = i32>
を実装している何か」を返す
具体的な型( Vec<i32>::IntoIter
) は外部に漏れない
呼び出し側視点でも、 Iterator<Item = i32>
しか見えないということ
rustlet iter = get_iterator(); // Iterator<Item = i32>
コンパイル時に、 iter
の 型は具体的な IntoIter<i32>
に決まる
impl Iterator
のように見えるが、内部では型が決まっている
fn get_value() -> Box<dyn Iterator<Item = i32>>
になり、ヒープ確保が発生する
もし、これ↑がgenericsだったら、呼び出し側は具体的な型が見えるはず
genericsは関数の呼び出し時に型を決める
rustfn get_iterator<T: Iterator<Item = i32>>() -> T {
vec![1, 2, 3].into_iter() // ❌ コンパイルエラー
}
戻り値の impl Trait
は 「存在型(existential type)」 に相当します。
存在型とは?
「ある T
が存在するが、具体的な型は公開しない」 という型の概念
impl Trait
の戻り値は 「何らかの型が存在するが、それが何かは明かさない」 という性質を持つ
「ある T
があるが、それが何かは言わない」 → これは 存在型
ジェネリクス ( T: Trait
) との違い
ジェネリクス ( T
) は 「全ての T
に対して成り立つ」 (∀)
「任意の T
に対して動作する」
fn foo<T: Trait>(x: T) {}
は どんな T
でも受け付ける
impl Trait
は 「ある特定の型 T
があるが、それを隠す」 (∃)
「何かの T
があるが、それが何かは公開しない」
fn foo() -> impl Trait
は ある T
を返すが、それが何かは隠す
数学的には:
ジェネリクス ( T: Trait
) → 「全称型 (∀T)」 ( forall T.
)
impl Trait
(戻り値) → 「存在型 (∃T)」 ( exists T.
)
このため、戻り値の impl Trait
は 存在型の一種 であり、ジェネリクス ( T: Trait
) とは本質的に異なるもの です。