なぜトレイトオブジェクトが不完全なのかRustでdyn Traitを使用しようとした際、その関連型が未定義のままだとerror[E0191]が発生します。関連型は、トレイト内のプレースホルダーのようなものだと考えてください。コンパイラが動的ディスパッチのためのvtable(仮想関数テーブル)を構築する前に、それらのプレースホルダーに何が入るのかを正確に知る必要があります。
dynキーワードを使用すると、Rustは関数アドレスのテーブルへのポインタを作成します。これには、すべての戻り値の正確なサイズと型を知る必要があります。例えばIteratorのItem型が不明な場合、コンパイラは各イテレーション(反復)のためにどれだけのスタック領域を確保すべきかを決定できません。
問題が発生するコード多くの開発者は、イテレータをBox化しようとする際にこの問題に直面します。以下は、このエラーを引き起こすコードスニペットです。
fn process_data(data: Box<dyn Iterator>) {
for item in data {
println!("{:?}", item);
}
}
fn main() {
let my_vec = vec![1, 2, 3];
let boxed_iter = Box::new(my_vec.into_iter());
process_data(boxed_iter);
}
これをコンパイルしようとすると、以下のような具体的なエラーが表示されます。
error[E0191]: the value of the associated type 'Item' (from trait 'std::iter::Iterator') must be specified
--> src/main.rs:1:28
|
1 | fn process_data(data: Box<dyn Iterator>) {
| ^^^^^^^^^^^^ help: specify the associated type: `Iterator<Item = Type>`
エラーを修正する3つの方法このエラーを解決するには、トレイトがどのような特定の型を扱うのかをコンパイラに伝える必要があります。以下に、最も効果的なアプローチを紹介します。
1. 関連型をバインドするイテレータが常にi32やStringなどの特定の型を返すことがわかっている場合は、山括弧(アングルブラケット)の中でそれを宣言します。これにより、vtableのための型定義が完成します。
// このイテレータが32ビット整数を返すことを指定する
fn process_data(data: Box<dyn Iterator<Item = i32>>) {
for item in data {
println!("{:?}", item);
}
}
2. 静的ディスパッチ(ジェネリクス)に切り替える動的ディスパッチは、ポインタの間接参照により、通常1コールあたり10〜30ナノ秒程度のわずかな実行時コストが発生します。単一のコレクションに異なる種類のイテレータを格納する必要がない限り、ジェネリクスの方が高速な代替手段となります。ジェネリクスを使用すると、コンパイラはコードをインライン化し、特定の型に合わせてロジックを最適化できます。
// 高速でゼロコストな静的ディスパッチ
fn process_data<I>(data: I)
where
I: Iterator<Item = i32>
{
for item in data {
println!("{:?}", item);
}
}
3. 「impl Trait」を活用する関数からイテレータを返す際、Map<Filter<VecIter>>のような複雑な型を書き出したくない場合があります。impl Traitを使用すると、Boxによるヒープ割り当てを避けつつ、コードをクリーンに保つことができます。
fn get_numbers() -> impl Iterator<Item = i32> {
vec![1, 2, 3].into_iter().map(|x| x * 2)
}
カスタムトレイトでの処理このルールは自作のトレイトにも適用されます。トレイトでtypeを定義している場合、そのトレイトへのdyn参照は、その型が何であるかを定義しなければなりません。
trait Processor {
type Input;
fn process(&self, input: Self::Input);
}
// エラー:Rustはここで'Input'が何であるかを知りません
// fn run(p: &dyn Processor) { ... }
// 正解:InputをStringとして明示的に定義する
fn run(p: &dyn Processor<Input = String>) {
p.process("hello".to_string());
}

