Progrust Library.

// dictionary

トレイト.

トレイトは、「この型はこういう振る舞いを持つ」という共通のインターフェースを定義する仕組みです。trait宣言でメソッドのシグネチャを並べ、impl トレイト名 for 型名 { ... }で型ごとに中身を実装します。同じトレイトを実装した型は、型が違っても同じメソッド名で同じように扱えます。

// 「説明文を返せる」という振る舞いを定義する
trait Describe {
    fn describe(&self) -> String;
}

struct Book {
    title: String,
    price: u32,
}

// 自分で定義した型に実装する
impl Describe for Book {
    fn describe(&self) -> String {
        format!("書籍『{}』 {}円", self.title, self.price)
    }
}

// 標準ライブラリの型にも、自分のトレイトなら実装できる
impl Describe for u32 {
    fn describe(&self) -> String {
        format!("数値 {}", self)
    }
}

fn main() {
    let book = Book {
        title: String::from("Rust入門"),
        price: 3200,
    };
    println!("{}", book.describe());
    println!("{}", 42.describe());
}
Playgroundで開く

宣言と実装の規則

トレイトの本体にはメソッドや関連関数のほか、関連定数・関連型も置けます1。本体を省略した(;で終わる)メソッドは実装側で必ず定義しなければならず、トレイト側に本体を書いておくとそれがデフォルト実装(既定の実装)になります2。

実装側(impl Trait for Type)は、トレイトが要求する項目をすべて定義する必要があり(足りないとエラー: E0046)、トレイトにないメソッドを勝手に追加することもできません(エラー: E0407。関連型ならE0437、関連定数ならE0438)3。型に固有のメソッドを追加するimplブロック(impl 型名 { ... })とは、この点で役割が異なります。

他言語のインターフェースとの違い

Java・C#・Goなどの「インターフェース」と似ていますが4、次の点が異なります。

観点 多くの言語のインターフェース Rustのトレイト
実装を書く場所 型の定義と一体(class X implements Y) 型の定義とは別のimplブロック。後付けできる
他所の型への実装 基本的に不可 自分のトレイトなら標準ライブラリの型にも実装できる(上の例のu32)
メソッドの呼び出し 常に呼べる トレイトがスコープ内にあるときだけ呼べる5

クローン・Copyトレイト・変換トレイトなど、標準ライブラリの機能の多くもこの「後付けできる振る舞い」の形で提供されています。

オーファンルール(コヒーレンス)

トレイトの実装は、トレイトか、実装に現れる型の少なくとも1つが自分のクレートで定義されているときだけ書けます6(impl Trait<A> for BならA・Bのどちらかが自分の型であればよい)。外部のトレイトを外部の型に実装しようとするとエラー: E0117になります。この規則をオーファンルール(孤児の規則)と呼びます。

use std::fmt;

// Display(std)を Vec<i32>(std)に実装しようとしている
impl fmt::Display for Vec<i32> { // エラー: E0117(トレイトも型も外部のもの)
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}個の要素", self.len())
    }
}

この規則がないと、2つの外部クレートが同じトレイトを同じ型に別々に実装でき、依存を1つ追加しただけで実装が衝突してコンパイルが通らなくなる事態が起こりえます6。「1つの型に対する1つのトレイトの実装は、プログラム全体で1つに定まる」という性質をコヒーレンス(一貫性)と呼び、オーファンルールはそれを保証するための規則です。

補足

外部トレイトを外部型に実装したいとき

どうしても外部の型に外部のトレイトを実装したい場合は、外部の型を自分の構造体で包んだニュータイプ(struct Wrapper(Vec<i32>);のようなタプル構造体)を用意し、そのラッパーに実装します。ラッパーは自分のクレートの型なので、オーファンルールに違反しません。

use std::fmt;

struct Wrapper(Vec<i32>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}個の要素", self.0.len())
    }
}

fn main() {
    let scores = Wrapper(vec![80, 95, 70]);
    println!("{}", scores); // 3個の要素
}
Playgroundで開く
本項で扱わない発展的な機能

トレイトには、ここで扱った基本のほかに次のような使い方があります。

  • トレイト内ではSelfという型が暗黙に使え、「このトレイトを実装している型」を指します7
  • ジェネリクスの型引数に「このトレイトを実装していること」を要求するトレイト境界
  • dyn トレイト名で、異なる型の値を同じトレイトの実装として実行時に切り替える(トレイトオブジェクト)
  • derive属性(#[derive(...)])で標準トレイトの実装を自動生成する
  • +や==などの演算子の振る舞いを、対応するトレイトの実装で定義する(演算子オーバーロード)

Footnotes

  1. The Rust Reference - Traits “This interface consists of associated items, which come in three varieties: functions, types, constants” ↩

  2. The Rust Reference - Traits “Trait functions may omit the function body by replacing it with a semicolon. … If the trait function defines a body, this definition acts as a default for any implementation which does not override it.” ↩

  3. The Rust Reference - Trait implementations “A trait implementation must define all non-default associated items declared by the implemented trait, may redefine default associated items defined by the implemented trait, and cannot define any other items.” ↩

  4. The Rust Programming Language - Traits: Defining Shared Behavior “Traits are similar to a feature often called interfaces in other languages, although with some differences.” ↩

  5. The Rust Reference - Method-call expressions “Any of the methods provided by a visible trait implemented by T.” ↩

  6. The Rust Reference - Orphan rules “The orphan rule states that a trait implementation is only allowed if either the trait or at least one of the types in the implementation is defined in the current crate. It prevents conflicting trait implementations across different crates and is key to ensuring coherence.” ↩ ↩2

  7. The Rust Reference - Traits “All traits define an implicit type parameter Self that refers to ‘the type that is implementing this interface’.” ↩

この辞書が使われているページ

backlinks 35

  1. 辞書Copyトレイト
  2. 辞書Debugトレイト
  3. 辞書Defaultトレイト
  4. 辞書derive属性
  5. 辞書Displayトレイト
  6. 辞書impl Trait
  7. 辞書implブロック
  8. 辞書main関数
  9. 辞書never型
  10. 辞書pub
  11. 辞書unwrap
  12. 辞書use宣言
  13. 辞書クローン
  14. 辞書コンソール出力
  15. 辞書ジェネリクス
  16. 辞書デフォルト実装
  17. 辞書トレイトオブジェクト
  18. 辞書トレイト境界
  19. 辞書マクロ
  20. 辞書モジュール
  21. 辞書ユニット様構造体
  22. 辞書演算子オーバーロード
  23. 辞書関連型
  24. 辞書自動参照・自動参照外し
  25. 辞書静的変数
  26. 辞書範囲式
  27. 辞書比較のトレイト
  28. 辞書比較演算子
  29. 辞書標準ライブラリ
  30. 辞書文字列型
  31. 辞書変換トレイト
  32. 本動かして学ぶRustプログラミング問題集 › 第18章 トレイト
  33. 本動かして学ぶRustプログラミング問題集 › 第19章 derive属性と標準トレイト
  34. 本動かして学ぶRustプログラミング問題集 › 第20章 トレイト境界とimpl Trait
  35. 本動かして学ぶRustプログラミング問題集 › 第21章 トレイトオブジェクトと関連型