rust-traits - 解説

実装の詳細

トレイトの基本構造

// トレイト定義
trait MyTrait {
    // 必須メソッド(実装者が定義)
    fn required(&self) -> i32;

    // デフォルト実装(オーバーライド可能)
    fn with_default(&self) -> i32 {
        self.required() * 2
    }

    // 関連定数
    const MAX: i32 = 100;

    // 関連型
    type Output;
}

実装の分類

// 1. 自分の型に自分のトレイト
struct MyType;
trait MyTrait {}
impl MyTrait for MyType {} // OK

// 2. 自分の型に外部のトレイト
impl std::fmt::Display for MyType {} // OK

// 3. 外部の型に自分のトレイト
impl MyTrait for String {} // OK

// 4. 外部の型に外部のトレイト
impl std::fmt::Display for Vec<i32> {} // NG (孤児規則)

よくある間違い

1. Self の使用位置

trait Factory {
    // Self はトレイトオブジェクトで使用不可
    fn create() -> Self; // オブジェクト安全でない

    // 代替: Box を返す
    fn create_boxed() -> Box<dyn Factory>
    where
        Self: Sized;
}

2. ジェネリックメソッド

trait Process {
    // ジェネリックメソッドはオブジェクト安全でない
    fn process<T>(&self, item: T); // NG for dyn

    // 代替: 具体的な型または関連型
    fn process_str(&self, item: &str);
}

3. 境界の過不足

// 不足: コンパイルエラー
fn print<T>(item: T) {
    println!("{}", item); // T: Display が必要
}

// 過剰: 不要な制約
fn store<T: Display + Debug + Clone + Send + Sync>(item: T) {
    let _ = item; // 何も使っていない
}

// 適切: 必要最小限
fn print<T: Display>(item: T) {
    println!("{}", item);
}

トレイトオブジェクト

vtable の仕組み

┌─────────────────┐
│ dyn Trait       │
├─────────────────┤
│ data: *const T  │ → 実際のデータ
│ vtable: *const  │ → vtable
└─────────────────┘

vtable:
┌─────────────────┐
│ drop            │ → デストラクタ
│ size            │ → サイズ
│ align           │ → アライメント
│ method1         │ → メソッド1のアドレス
│ method2         │ → メソッド2のアドレス
└─────────────────┘

使い分け

// 静的ディスパッチ(ジェネリクス)
// - コンパイル時に型が決定
// - インライン化可能
// - バイナリサイズ増加
fn process_static<T: Document>(doc: T) {}

// 動的ディスパッチ(トレイトオブジェクト)
// - 実行時に型が決定
// - vtableオーバーヘッド
// - 異なる型を同じコレクションに
fn process_dynamic(doc: &dyn Document) {}

高度なパターン

スーパートレイト

// Debug を実装していないと MyTrait は実装できない
trait MyTrait: Debug {
    fn my_method(&self);
}

impl<T: Debug> MyTrait for T {
    fn my_method(&self) {
        println!("{:?}", self);
    }
}

ブランケット実装

// Display を実装している全ての型に ToString を実装
impl<T: Display> ToString for T {
    fn to_string(&self) -> String {
        format!("{}", self)
    }
}

マーカートレイト

// 実装なし、型の性質を示す
trait Sendable {}
trait Storable {}

// 複数のマーカーを要求
fn send_and_store<T: Sendable + Storable>(item: T) {}

パフォーマンス考慮事項

インライン化の影響

// ジェネリクス: インライン化される可能性が高い
fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
    a + b
}

// トレイトオブジェクト: インライン化されない
fn add_dyn(a: &dyn std::ops::Add<Output = i32>, b: i32) -> i32 {
    // 間接呼び出し
}

単相化のコスト

// 型ごとにコード生成
process::<i32>();
process::<i64>();
process::<f32>();
// → 3つの関数が生成される

// バイナリサイズが問題なら dyn を検討
fn process(item: &dyn Display) {
    // 1つの関数のみ
}