4 分で読了 #rust #type-system
このページの目次

struct と trait

struct には3種類(名前付きフィールド/タプル/ユニット)があり、trait は Rust における「インターフェース」への回答です——一連のメソッドシグネチャを定義し、任意の型がそれを実装できます。derive マクロは struct に対して一般的な trait(Clone/Debug/Serialize)を自動生成し、orphan rule(孤ルール)は「誰が誰の trait を実装できるか」を制限し、依存関係の競合を防ぎます。

struct: 「データの集合体」以上のもの

struct Point { x: f64, y: f64 }      // named fields — 最も一般的
struct Color(u8, u8, u8);            // tuple struct — 名前なしフィールド、単純なラップに適す
struct AlwaysEqual;                   // unit struct — ZST、マーカー型として使用

タプル構造体は「新しい型のラップ」に適しています——単一フィールドの struct で基盤となる型をラップし、新しい型のセマンティクスを与えて誤用を防ぎます:

struct Meters(f64);                  // f64 ではない — Meters だ!
struct Feet(f64);

let m = Meters(100.0);
let f = Feet(328.0);
// let sum = m.0 + f.0;              // こうすることは可能だが、セマンティクスとして直接比較すべきではない
// コンパイラはこれを阻止しないが、型システムが「これはメートル」「あれはフィート」を区別してくれる

trait: 振る舞いの契約

trait は「一連のメソッドシグネチャ」を定義します——この trait を実装するあらゆる型は、これらのメソッドを提供しなければなりません:

trait Summary {
    fn summarize(&self) -> String;    // 実装必須 (デフォルトなし)
    
    fn default_summary(&self) -> String {  // デフォルト実装あり、上書きは任意
        String::from("(続きを読む...)")
    }
}

impl Summary for Point {
    fn summarize(&self) -> String {
        format!("Point({}, {})", self.x, self.y)
    }
}

trait は Java/C# の interface に似ていますが、重要な違いがあります:⁠外部型に対して独自の trait を実装できる⁠(ただし、外部型に対して外部 trait を実装することはできません——これを孤ルールと呼びます)。

Derive: コンパイラによる自動コード生成

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Point { x: f64, y: f64 }

derive は、すべてのフィールドが対応する trait を実装していることを要求します。例えば #[derive(Clone)] は、各フィールドが Clone であることを要求します。コンパイラはこれらの制約をチェックし、テンプレート化された impl を生成します——マクロやコード生成ツールは不要です。

Orphan Rule (孤ルール): なぜ気軽に impl できないのか

// 自前の crate: my_lib
impl Display for MyType { ... }      // OK: MyType は自前
impl MyTrait for String { ... }      // OK: MyTrait は自前
impl Display for Vec<MyType> { ... } // OK: Vec の引数 MyType は自前

// impl Display for String { ... }   // ERROR: Display(std) + String(std)
//     両方とも他の crate で定義されている → orphan rule により禁止

このルールは、2つの crate が同じ (型, trait) の組み合わせに対して競合する impl を同時に提供することを防ぎます——もし発生した場合、コンパイラはどちらを選ぶべきか判断できず、リンク時の整合性チェックでエラーになります。これは Rust の trait システムにおけるモジュール化の核心的な保証です。

Trait Bounds: ジェネリクスへの制約

// 単一の bound — 構文糖衣
fn notify(item: &impl Summary) { }

// 等価なジェネリック記法
fn notify<T: Summary>(item: &T) { }

// 複数の bounds
fn notify<T: Summary + Display>(item: &T) { }

// where 節 — 複雑な bounds でより可読性向上
fn complex<T, U>(t: &T, u: &U) -> T::Output
where
    T: Display + Clone,
    U: Clone + Debug,
    T: Add<U>,
{ ... }

参考

  • Rust Book: 第5章 (struct)、第10.2章 (trait)
  • Rust Reference: Traits, orphan rules, derive

Keywords: struct, trait, impl, derive, orphan rule, coherence, trait bound, where clause