Progrust Library.

// dictionary

デフォルト実装.

デフォルト実装は、トレイトの宣言でメソッドのシグネチャだけでなく本体まで書いておく仕組みです。本体を;で終えたメソッドは実装側で必ず定義しなければなりませんが、本体を書いておくと「実装側が上書きしなかったときに使われる既定の処理」になります1。

実装側は上書きしたいメソッドだけを書けばよく、すべてデフォルトのままで足りるなら中身が空のimplブロック(impl Trait for Type {})でも構いません2。

trait Item {
    // 本体なし: 実装側で必ず定義する
    fn name(&self) -> String;
    fn price(&self) -> u32;

    // 本体あり: これがデフォルト実装になる
    fn label(&self) -> String {
        format!("{} {}円", self.name(), self.price())
    }
}

struct Book {
    title: String,
}

impl Item for Book {
    fn name(&self) -> String {
        self.title.clone()
    }
    fn price(&self) -> u32 {
        3200
    }
    // label は書かない → デフォルト実装が使われる
}

struct GiftSet;

impl Item for GiftSet {
    fn name(&self) -> String {
        String::from("ギフトセット")
    }
    fn price(&self) -> u32 {
        5000
    }
    // label を同じ名前で定義する → 上書きされる
    fn label(&self) -> String {
        format!("【ギフト】{} {}円(送料無料)", self.name(), self.price())
    }
}

fn main() {
    let book = Book {
        title: String::from("Rust入門"),
    };
    println!("{}", book.label()); // Rust入門 3200円
    println!("{}", GiftSet.label()); // 【ギフト】ギフトセット 5000円(送料無料)
}
Playgroundで開く

デフォルト実装からのメソッド呼び出し

デフォルト実装の本体では、selfを通じて同じトレイトの他のメソッドを呼べます。呼ぶ相手にデフォルト実装がなくても構いません3。上のlabelは、実装側が必ず定義するnameとpriceを組み立てているだけです。

この形にすると、トレイト側で多くの機能を提供しながら、実装側に要求するのはごく一部で済みます3。実際に呼ばれるのはその型の実装で選ばれたメソッドなので、nameを上書きすればlabelの出力も自動で変わります。

上書きの規則

実装側の書き方 結果
デフォルトのあるメソッドを書かない デフォルト実装がそのまま使われる
デフォルトのあるメソッドを同名で定義する その定義で上書きされる4
デフォルトのない項目(メソッド・関連定数・関連型)を書かない エラー: E0046(必須の項目が足りない)

上書きは「置き換え」であり、上書きした実装の中からそのメソッドのデフォルト実装を呼び出すことはできません5。デフォルトの処理を土台にして前後に処理を足したい場合は、共通部分を別のメソッド(デフォルト実装付き)に切り出しておく必要があります。

補足

標準ライブラリでの使われ方

標準ライブラリの主要なトレイトは、この仕組みで「必須は最小限、残りはデフォルト」という形に整理されています。

トレイト 必須のメソッド デフォルト実装を持つ主なメソッド
Iterator next(と関連型Item) map・filter・sumなど多数6
PartialEq eq ne(eqの否定。「よほどの理由がない限り上書きすべきでない」と明記されている)7
Ord cmp max・min・clamp8

Iteratorを自作の型に実装するときnextだけ書けば大量のメソッドが使えるようになるのは、それらがすべてnextを呼ぶデフォルト実装だからです。

関連定数・関連型のデフォルト

デフォルトを持てるのはメソッドだけではありません。関連定数は=と値を書いておけばデフォルト値になり、逆に=以降を省略すると実装側での定義が必須になります9。

一方、関連型にデフォルトの型は書けません。型を指定できるのは実装側だけです9(安定版Rustの場合。nightly限定のassociated_type_defaults機能として提案中)。

trait Tax {
    const RATE: u32 = 10; // デフォルト値付きの関連定数

    fn price(&self) -> u32;

    fn price_with_tax(&self) -> u32 {
        self.price() * (100 + Self::RATE) / 100
    }
}

struct Book;
impl Tax for Book {
    const RATE: u32 = 8; // 書籍は軽減税率として上書き
    fn price(&self) -> u32 {
        3200
    }
}

struct Pen;
impl Tax for Pen {
    // RATE は書かない → デフォルトの 10 が使われる
    fn price(&self) -> u32 {
        150
    }
}

fn main() {
    println!("{}円", Book.price_with_tax()); // 3456円
    println!("{}円", Pen.price_with_tax()); // 165円
}
Playgroundで開く

Footnotes

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

  2. The Rust Programming Language - Default Implementations “To use a default implementation to summarize instances of NewsArticle, we specify an empty impl block with impl Summary for NewsArticle {}.” ↩

  3. The Rust Programming Language - Default Implementations “Default implementations can call other methods in the same trait, even if those other methods don’t have a default implementation. In this way, a trait can provide a lot of useful functionality and only require implementors to specify a small part of it.” ↩ ↩2

  4. 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.” ↩

  5. The Rust Programming Language - Default Implementations “Note that it isn’t possible to call the default implementation from an overriding implementation of that same method.” ↩

  6. std::iter::Iterator Required Methods にnextのみが挙げられ、その他は Provided Methods として列挙されている。 ↩

  7. std::cmp::PartialEq “The default implementation of ne provides this consistency and is almost always sufficient. It should not be overridden without very good reason.” ↩

  8. std::cmp::Ord Required Methods はcmpのみで、max・min・clampは Provided Methods。 ↩

  9. The Rust Reference - Traits “Similarly, associated constants may omit the equal sign and expression to indicate implementations must define the constant value. Associated types must never define the type, the type may only be specified in an implementation.” ↩ ↩2

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

backlinks 5

  1. 辞書Defaultトレイト
  2. 辞書トレイト
  3. 辞書関連型
  4. 辞書比較のトレイト
  5. 本動かして学ぶRustプログラミング問題集 › 第18章 トレイト