m04-zero-cost について
m04-zero-costは、型の抽象化方法を選ぶRust開発者向けの学習スキルです。ディスパッチ、性能、コンパイル時間のトレードオフを通じて、ジェネリクス、トレイトオブジェクト、enumを比較します。
主な機能
- 01抽象レベルを選ぶための問い
- 02静的・動的ディスパッチの比較
- 03ジェネリクス、トレイトオブジェクト、enumの指針
- 04オブジェクトセーフ性の確認
- 05Rustエラーコードのガイド
活用シーン
- 01性能重視コードのディスパッチ選択
- 02異種コレクションの設計
- 03プラグインアーキテクチャの型の検討
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ actionbook/ rust-skills --skill m04-zero-costインストール後、Claude Code で/m04-zero-costを入力して読み込まれたか確認します。
詳細
ゼロコスト抽象化
レイヤー 1:言語の仕組み
中心となる問い
コンパイル時または実行時のポリモーフィズムが必要ですか?
ジェネリクスとトレイトオブジェクトのどちらかを選ぶ前に、次を確認します。
- コンパイル時に型が判明していますか?
- 異種コレクションが必要ですか?
- パフォーマンスで何を優先しますか?
エラー → 設計上の問い
| エラー | ただ言うのではなく | 代わりに問う |
|---|---|---|
| E0277 | 「トレイト境界を追加する」 | この抽象化は適切なレベルですか? |
| E0308 | 「型を修正する」 | 型を統一するべきですか、それとも別々にするべきですか? |
| E0599 | 「トレイトをインポートする」 | このトレイトは適切な抽象化ですか? |
| E0038 | 「オブジェクトセーフにする」 | 動的ディスパッチは本当に必要ですか? |
思考のヒント
トレイト境界を追加する前に、次を確認します。
-
どのような抽象化が必要ですか?
- 動作は同じで型が異なる → トレイト
- 動作は異なり、型は同じ → enum
- 抽象化は不要 → 具象型
-
型はいつ判明しますか?
- コンパイル時 → ジェネリクス(静的ディスパッチ)
- 実行時 → トレイトオブジェクト(動的ディスパッチ)
-
どのトレードオフを優先しますか?
- パフォーマンス → ジェネリクス
- コンパイル時間 → トレイトオブジェクト
- 柔軟性 → 状況によります
上位をたどる ↑
型システムに阻まれた場合:
E0277 (trait bound not satisfied)
↑ Ask: Is the abstraction level correct?
↑ Check: m09-domain (what behavior is being abstracted?)
↑ Check: m05-type-driven (should use newtype?)
| 解消しないエラー | たどり先 | 問い |
|---|---|---|
| 複雑なトレイト境界 | m09-domain | 抽象化は適切ですか? |
| オブジェクトセーフティの問題 | m05-type-driven | typestate が役立ちますか? |
| 型の爆発 | m10-performance | dyn のオーバーヘッドを許容できますか? |
下位をたどる ↓
設計から実装へ:
"Need to abstract over types with same behavior"
↓ Types known at compile time → impl Trait or generics
↓ Types determined at runtime → dyn Trait
"Need collection of different types"
↓ Closed set → enum
↓ Open set → Vec<Box<dyn Trait>>
"Need to return different types"
↓ Same type → impl Trait
↓ Different types → Box<dyn Trait>
クイックリファレンス
| パターン | ディスパッチ | コードサイズ | 実行時コスト |
|---|---|---|---|
fn foo<T: Trait>() | 静的 | +肥大化 | ゼロ |
fn foo(x: &dyn Trait) | 動的 | 最小 | vtable 検索 |
impl Trait return | 静的 | +肥大化 | ゼロ |
Box<dyn Trait> | 動的 | 最小 | 割り当て + vtable |
構文の比較
// Static dispatch - type known at compile time
fn process(x: impl Display) { } // argument position
fn process<T: Display>(x: T) { } // explicit generic
fn get() -> impl Display { } // return position
// Dynamic dispatch - type determined at runtime
fn process(x: &dyn Display) { } // reference
fn process(x: Box<dyn Display>) { } // owned
エラーコードリファレンス
| エラー | 原因 | 簡単な修正 |
|---|---|---|
| E0277 | 型がトレイトを実装していない | impl を追加するか、境界を変更する |
| E0308 | 型が一致しない | ジェネリックパラメーターを確認する |
| E0599 | メソッドが見つからない | use でトレイトをインポートする |
| E0038 | トレイトがオブジェクトセーフではない | ジェネリクスを使うか、設計を見直す |
選択ガイド
| シナリオ | 選択 | 理由 |
|---|---|---|
| パフォーマンスが重要 | ジェネリクス | 実行時コストがゼロ |
| 異種コレクション | dyn Trait | 実行時に型が異なる |
| プラグインアーキテクチャ | dyn Trait | コンパイル時に型が不明 |
| コンパイル時間の短縮 | dyn Trait | 単相化が少ない |
| 小規模で既知の型セット | enum | 間接参照がない |
オブジェクトセーフティ
トレイトが次の条件を満たす場合、オブジェクトセーフです。
Self: Sized境界を持たないSelfを返さない- ジェネリックメソッドを持たない
- オブジェクトセーフでないメソッドには
where Self: Sizedを使う
アンチパターン
| アンチパターン | 問題点 | より良い方法 |
|---|---|---|
| すべてを過度にジェネリックにする | コンパイル時間、複雑さ | 可能な場合は具象型を使う |
型が既知なのに dyn を使う | 不要な間接参照 | ジェネリクス |
| 複雑なトレイト階層 | 理解しにくい | よりシンプルな設計 |
| オブジェクトセーフティを無視する | 柔軟性が制限される | 必要に応じて dyn を使えるよう計画する |
関連スキル
| 場合 | 参照先 |
|---|---|
| 型駆動設計 | m05-type-driven |
| ドメインの抽象化 | m09-domain |
| パフォーマンスに関する懸念 | m10-performance |
| Send/Sync 境界 | m07-concurrency |
コメント
Markdown 対応