静的ディスパッチと動的ディスパッチ
トレードオフ
「静的ディスパッチと動的ディスパッチ」はCoddyKit上の無料Learn Rust Codingレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLearn Rust Coding学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Learn Rust Codingコースには全4レッスンが含まれています。
ディスパッチの2つの方法
トレイトメソッドを呼び出すとき、Rustはどの具体的な実装を実行するか決める必要があります。方法は2つあります。静的ディスパッチ(コンパイル時に決定)と動的ディスパッチ(実行時に決定)です。
ジェネリックによる静的ディスパッチ
ジェネリック関数は静的ディスパッチを使います。コンパイラーは具体的な型ごとに特化したコピーを生成します。この処理を単相化(monomorphization)と呼びます。
trait Area { fn area(&self) -> f64; }
struct Square { s: f64 }
impl Area for Square { fn area(&self) -> f64 { self.s * self.s } }
fn print_area<T: Area>(shape: &T) {
println!("{}", shape.area());
}
fn main() {
print_area(&Square { s: 4.0 });
}単相化
呼び出しで使う型ごとに、コンパイラーは関数の専用バージョンを生成します。メソッド呼び出しは検索を伴わない直接呼び出しになるため、手書きのコードと同じように高速です。
dynによる動的ディスパッチ
トレイトオブジェクトは動的ディスパッチを使います。1つの関数ですべての型を処理し、実行時にvtableを通じてメソッドのアドレスを見つけます。
trait Area { fn area(&self) -> f64; }
struct Square { s: f64 }
impl Area for Square { fn area(&self) -> f64 { self.s * self.s } }
fn print_area(shape: &dyn Area) {
println!("{}", shape.area());
}
fn main() {
print_area(&Square { s: 4.0 });
}vtable
トレイトオブジェクトはファットポインターです。一方はデータを指し、もう一方はメソッドのアドレスを列挙したvtableを指します。呼び出しのたびに、このテーブルの該当箇所を参照します。
この追加の間接参照が実行時のコストです。
速度のトレードオフ
静的ディスパッチは呼び出しごとに高速で、インライン化も可能ですが、より多くのマシンコードを生成します。動的ディスパッチはわずかな間接参照を追加する一方、コードサイズを小さく保てます。
ほとんどのアプリでは差は無視できる程度です。柔軟性を基準に選びましょう。
コードサイズのトレードオフ
ジェネリック関数を多数の型で呼び出すと、多数のコピーが作られ、バイナリが肥大化することがあります。単一のdyn関数を使えば、この重複を避けられます。
このため、ライブラリでは内部実装にトレイトオブジェクトを使うことがあります。
柔軟性のトレードオフ
ジェネリックでは呼び出し箇所ごとに1つの具体的な型が必要なため、コレクションに異なる型を混在させられません。トレイトオブジェクトなら可能です。異種のリストが必要なら、動的ディスパッチが答えです。
trait Area { fn area(&self) -> f64; }
struct Square { s: f64 }
struct Rect { w: f64, h: f64 }
impl Area for Square { fn area(&self) -> f64 { self.s * self.s } }
impl Area for Rect { fn area(&self) -> f64 { self.w * self.h } }
fn main() {
let shapes: Vec<Box<dyn Area>> = vec![Box::new(Square { s: 2.0 }), Box::new(Rect { w: 3.0, h: 4.0 })];
let total: f64 = shapes.iter().map(|s| s.area()).sum();
println!("{}", total);
}両方を組み合わせる
両者を組み合わせることもできます。ジェネリック関数が境界でimpl Traitを受け取り、内部では値をBox<dyn Trait>として格納する、といった使い方です。それぞれ適した場面で使いましょう。
選択の指針
簡単なルールを示します。
- 呼び出しごとに1つの型で、ホットパスにある場合は、ジェネリック(静的)を使います。
- コレクションに異なる型を入れる場合は、
dyn(動的)を使います。 - バイナリサイズが気になる場合は、
dyn寄りにします。
重要な部分ではどちらもゼロコスト
Rustは、要求していないオーバーヘッドを追加しません。ジェネリックの実行時コストはゼロで、トレイトオブジェクトのコストは1回のポインター間接参照だけです。トレードオフは明示的に選択できます。
クイックチェック
ディスパッチについての知識を確認しましょう。
まとめ
2種類のディスパッチ方式を比較しました。
- 静的(ジェネリック):単相化され、高速で、バイナリは大きくなり、呼び出しごとに1つの型を使う
- 動的(
dyn):vtableを検索し、柔軟で、コードが小さく、混在したコレクションを利用できる - 柔軟性、性能、バイナリサイズの要件に基づいて選択する
よくある質問
「静的ディスパッチと動的ディスパッチ」レッスンは無料ですか?
はい。「静的ディスパッチと動的ディスパッチ」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Learn Rust Codingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Learn Rust Codingコースには全4レッスンが含まれています。
「静的ディスパッチと動的ディスパッチ」で何を学びますか?
トレードオフ ブラウザで直接実行するハンズオンコードでLearn Rust Codingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Learn Rust Codingを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのLearn Rust Codingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「静的ディスパッチと動的ディスパッチ」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このLearn Rust Codingレッスンでコードを書いて実行できますか?
はい。すべてのLearn Rust Codingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- トレイトの定義
- トレイトオブジェクトとdyn
- 静的ディスパッチと動的ディスパッチ
- デフォルトメソッド