rust-traits - 背景

歴史的経緯

ポリモーフィズムの進化

  • 継承ベース(1967年〜)
- Simula で導入 - 「is-a」関係 - 実装の継承

  • インターフェース(1995年〜)
- Java で普及 - 実装と型の分離 - 多重継承の問題回避

  • 型クラス(1988年〜)
- Haskell で導入 - アドホック多相 - 実装の後付け可能

  • Rustトレイト(2010年〜)
- 型クラスに影響を受ける - ゼロコスト抽象化 - コヒーレンス規則

トレイトの設計原則

// トレイト = 型が持つべき振る舞いの定義
trait Greet {
    fn greet(&self) -> String;
}

// 既存の型にも後から実装可能
impl Greet for String {
    fn greet(&self) -> String {
        format!("Hello, {}!", self)
    }
}

コンピュータサイエンス的な意味

静的ディスパッチ(単相化)

fn print_twice<T: Display>(item: T) {
    println!("{}", item);
    println!("{}", item);
}

// コンパイル時に具体的な型ごとにコード生成
// print_twice::<i32>()
// print_twice::<String>()

動的ディスパッチ

fn print_any(item: &dyn Display) {
    println!("{}", item);
}

// vtable を通じた間接呼び出し
// 実行時にメソッドを解決

比較

方式 利点 欠点
静的 高速、インライン化 バイナリサイズ増加
動的 柔軟、サイズ一定 vtableオーバーヘッド

関連型 vs 型パラメータ

// 型パラメータ: 呼び出し側が型を決定
trait Container<T> {
    fn get(&self) -> T;
}

// 関連型: 実装側が型を決定
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

実践での活用

拡張メソッド

// 標準型に機能を追加
trait StringExt {
    fn truncate_at(&self, max_len: usize) -> &str;
}

impl StringExt for str {
    fn truncate_at(&self, max_len: usize) -> &str {
        if self.len() <= max_len {
            self
        } else {
            &self[..max_len]
        }
    }
}

// 使用
"hello world".truncate_at(5); // "hello"

依存性の注入

trait Database {
    fn query(&self, sql: &str) -> Vec<Row>;
}

struct UserService<D: Database> {
    db: D,
}

// テスト時にモックを注入
struct MockDb;
impl Database for MockDb {
    fn query(&self, _: &str) -> Vec<Row> { vec![] }
}

プラグインシステム

trait Plugin {
    fn name(&self) -> &str;
    fn execute(&self, input: &str) -> String;
}

struct PluginManager {
    plugins: Vec<Box<dyn Plugin>>,
}

実世界とのギャップ

Orphan Rule(孤児規則)

// 外部のトレイトを外部の型に実装できない
// impl Display for Vec<T> { } // エラー!

// 解決策: ニュータイプパターン
struct MyVec<T>(Vec<T>);
impl<T: Display> Display for MyVec<T> {
    // OK
}

オブジェクト安全性

trait NotObjectSafe {
    fn new() -> Self;  // Self を返すのでNG
    fn generic<T>(&self, t: T);  // ジェネリックメソッドはNG
}

// dyn NotObjectSafe は使用不可

トレイト境界の複雑さ

// 複雑な境界は where 句で整理
fn process<T, U, V>(a: T, b: U, c: V) -> String
where
    T: Display + Clone + Send,
    U: Debug + Default,
    V: Iterator<Item = T>,
{
    // ...
}