Progrust Library.

// dictionary

Defaultトレイト.

Defaultは、その型の既定値(デフォルト値)を返す標準ライブラリのトレイトです(std::default::Default)。要求されるのはfn default() -> Selfという関連関数1つだけで1、u32なら0、boolならfalse、文字列型なら空文字列というように、標準ライブラリの多くの型があらかじめ実装しています。

自作の構造体ではderive属性(#[derive(Default)])で導出でき、..Default::default()と構造体更新記法を組み合わせると、「一部のフィールドだけ指定し、残りは既定値で埋める」初期化が書けます。設定項目の多い型を毎回すべて書き並べずに作れるのが主な用途です。

#[derive(Debug, Default)]
struct OrderOptions {
    gift_wrap: bool,     // 贈答用ラッピング → false
    message: String,     // メッセージカード → 空文字列
    coupon: Option<u32>, // クーポン割引額   → None
}

fn main() {
    // すべて既定値のインスタンス
    let plain = OrderOptions::default();
    println!("{:?}", plain); // OrderOptions { gift_wrap: false, message: "", coupon: None }

    // ラッピングだけ指定し、残りは既定値で埋める
    let gift = OrderOptions {
        gift_wrap: true,
        ..Default::default()
    };
    println!("{:?}", gift); // OrderOptions { gift_wrap: true, message: "", coupon: None }
}
Playgroundで開く

Type::default()とDefault::default()

呼び出す関数は同じで、Selfがどう決まるかだけが違います。Defaultは標準ライブラリのpreludeに含まれるため、どちらの書き方でもuse宣言は要りません2。

書き方 Selfの決まり方
OrderOptions::default() 型名で明示する。読んだだけでどの型の既定値か分かる
Default::default() 周囲の文脈から型推論で決まる。let opts: OrderOptions = Default::default();や..Default::default()のように型が判明する位置で使う

型名を書ける場面ではType::default()のほうが読みやすく、..Default::default()のように型が自明な位置ではDefault::default()で型名の繰り返しを省けます。型を決められない位置で書くとエラー: E0790になります。

構造体更新記法との組み合わせ

構造体更新記法の..baseには同じ型の値を返す式を書けます。そこにDefault::default()を置いたのが..Default::default()で、既定値のインスタンスを基準に差分だけを指定する形になります。

このときDefaultを実装している必要があるのは構造体そのものであって、埋めたいフィールドの型ではありません。実装していない型に対して書くとエラー: E0277になります。

struct OrderOptions { // Default を実装していない
    gift_wrap: bool,
    message: String,
}

fn main() {
    // エラー: E0277(OrderOptions が Default を実装していない)
    let gift = OrderOptions { gift_wrap: true, ..Default::default() };
    println!("{}", gift.gift_wrap);
}
Playgroundで開く

deriveでの導出

#[derive(Default)]が生成するのは、全フィールドをそれぞれの型の既定値で埋めた値を返す実装です1。そのため導出には次の条件が付きます。

対象 条件
構造体 全フィールドの型がDefaultを実装していること(1つでも欠けるとエラー: E0277)
列挙型 既定にするバリアントを#[default]属性で指定すること(Rust 1.62以降)1。指定がないとエラー: E0665。指定できるのは値を持たないユニットバリアントだけで、値を持つバリアントを既定にしたい場合は手で実装する1
ジェネリクスを使った型 すべての型引数にDefaultのトレイト境界が付いた実装が生成される3

既定値が「ゼロ値・空」でよいならderiveで足り、0や空文字列以外を既定にしたい場合はimpl Default for 型名を手で書きます。導出の一般的な規則はderive属性の項目で扱っています。

補足

主な型の既定値

標準ライブラリの実装は、おおむね「ゼロ・空・なし」に揃えられています1(値はRust 1.93で確認)。

型 既定値
整数型・浮動小数点型 0 / 0.0
論理値型 false
文字型 '\0'(ヌル文字)
文字列型・ベクタ(Vec)・HashMapなどのコレクション 空
Option型 None
ユニット型 ()

一方Result型はDefaultを実装していません(Rust 1.93で確認)。OkとErrのどちらを既定とすべきかが型からは決まらないためです。Resultから既定値を得たいときは、後述のunwrap_or_defaultのようにOk側の型に対して使います。

既定値を自分で決める

0や空以外を既定にしたい場合は、impl Default for 型名でdefaultを実装します。このdefaultはメソッドではなくselfを取らない関連関数なので、Type::default()の形で呼ばれます。

#[derive(Debug)]
struct Shipping {
    fee: u32,
    express: bool,
}

// 通常配送・送料500円を既定とする
impl Default for Shipping {
    fn default() -> Self {
        Shipping {
            fee: 500,
            express: false,
        }
    }
}

fn main() {
    println!("{:?}", Shipping::default()); // Shipping { fee: 500, express: false }

    // 手で書いた実装でも ..Default::default() はそのまま使える
    let rush = Shipping {
        express: true,
        ..Default::default()
    };
    println!("{:?}", rush); // Shipping { fee: 500, express: true }
}
Playgroundで開く
Defaultが要求される場面

Defaultは、値を作る側だけでなく標準ライブラリの各所でトレイト境界として要求されます。

場面 挙動
unwrap系のunwrap_or_default None・ErrのときにTの既定値を返す(T: Default)
std::mem::take(&mut 値) 値を既定値に置き換え、元の値を返す(T: Default)4

mem::takeは、借用しかできない状況でフィールドの中身だけを持ち出したいときに使われます。

struct Cart {
    memo: String,
}

fn main() {
    let mut cart = Cart {
        memo: String::from("プレゼント用"),
    };
    // cart は借用したまま、memo の中身だけを取り出す
    let memo = std::mem::take(&mut cart.memo);
    println!("取り出した: {memo:?}"); // "プレゼント用"
    println!("残り: {:?}", cart.memo); // ""(既定値に置き換わっている)
}
Playgroundで開く
..Default::default()は既定値を丸ごと1つ作る

..Default::default()は「指定しなかったフィールドだけを個別に作る」記法ではありません。既定値のインスタンスを丸ごと1つ構築し、そこから必要なフィールドだけを取り出す動きになります。取り出されなかったフィールドはその場で破棄されます(リリースビルドで最適化されるかどうかは別の話です)。

既定値の生成が重い型では、この点が問題になることがあります。

dyn Defaultは作れない

Defaultはトレイトオブジェクト(dyn Default)には使えません1(エラー: E0038)。Sizedを継承している時点で不適合が確定し、加えて「selfを取らずSelfを返す関連関数」という形も単独で不適合の条件を満たします。「型が決まってはじめて既定値が決まる」トレイトなので、実行時に型を伏せる用途とは相容れません。

Footnotes

  1. std公式ドキュメント — Trait Default トレイト定義はpub trait Default: Sized { fn default() -> Self; }で、defaultは Required Methods に置かれている。“This trait can be used with #[derive] if all of the type’s fields implement Default. When derived, it will use the default value for each field’s type.” / “When using #[derive(Default)] on an enum, you need to choose which unit variant will be default. You do this by placing the #[default] attribute on the variant. … The #[default] attribute was stabilized in Rust 1.62.0.” / 各型の既定値は同ページの Implementors 一覧で確認できる。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7

  2. std公式ドキュメント — Module std::prelude std::prelude::v1の一覧にstd::default::Defaultが含まれる。 ↩

  3. The Rust Reference — Derive “The PartialEq derive macro emits an implementation of PartialEq for Foo<T> where T: PartialEq.”(組み込みderiveに共通する境界の付き方。Defaultでも同じ形の境界が付くことをRust 1.93で確認) ↩

  4. std公式ドキュメント — Function std::mem::take “Replaces dest with the default value of T, returning the previous dest value.” ↩

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

backlinks 2

  1. 辞書derive属性
  2. 本動かして学ぶRustプログラミング問題集 › 第19章 derive属性と標準トレイト