🦀

Rust Defaultについて調べてみた

2023/01/15に公開

Defaultとは?

名前の通り各タイプで実装されているdefault()の値を返します。

例えば

fn main() {
    let s = String::default();
    println!("String: {}", s);

    let int = i32::default();
    println!("Integer: {}", int);

    let str: &str = Default::default();
    println!("&str: {}", str);
}

これを実行すると下記の様になります。

$ cargo run

String: 
Integer: 0
&str: 

それぞれのTypeにはdefault();が実装されているので、{TypeName}::default()で定義された値を返している。

各タイプに実装されているdefault()の詳細は公式ドキュメントから確認できます。
例えばStringを見てみると String::new()を返してるそうです。

#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_default_impls", issue = "87864")]
impl const Default for String {
    /// Creates an empty `String`.
    #[inline]
    fn default() -> String {
        String::new()
    }
}

https://doc.rust-lang.org/src/alloc/string.rs.html#2263

#[derive(Default)] で struct にDefaultを設定

通常生成したオブジェクトに値を入れる時は下記の様な書き方になります。

struct Product {
    name: String,
    price: f64,
}

fn main() {
    let product = Product {
        name: "".to_string(),
        price: 0.0,
    };
    println!("{} price ${}", product.name, product.price);
}

# $ cargo run
# Name: , Price $0

上記の様にそれぞれ値を入れる必要があります。これでも良いのですが、実際のプロダクトではさらに多くの属性が含まれるため、いちいち値の入れるのはめんどくさいですね。

先程の Default::default();を使ってみましょう。

fn main() {
    let product : Product= Default::default();
    println!("Name: {}, Price ${}", product.name, product.price);
}

Buildしてみると以下のようなエラーになります。

$ cargo run
   Compiling app v0.1.0 (/Users/s14960/ghq/github.com/yuki0418/rust-practice/app)
error[E0277]: the trait bound `Product: Default` is not satisfied
 --> src/main.rs:7:28
  |
7 |     let product : Product= Default::default();
  |                            ^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Product`
  |
help: consider annotating `Product` with `#[derive(Default)]`
  |
1 | #[derive(Default)]
  |

For more information about this error, try `rustc --explain E0277`.
error: could not compile `app` due to previous error

これだと以下の様に怒られます。
理由はエラーでメッセージが出てる通り、 ProductタイプにはDefaultが実装されてないからです。

ProductDefaultを使ってみましょう。

#[derive(Default)] // <- Defaultを使う。
struct Product {
    name: String,
    price: f64,
}

fn main() {
    let product : Product= Default::default();
    println!("Name: {}, Price ${}", product.name, product.price);
    
    let named_product = Product {
        name: "Coffee".to_string(),
        ..Default::default()
    };
    println!("Name: {}, Price ${}", named_product.name, named_product.price);
}

これを実行すると

% $ cargo run
Name: , Price $0

それぞれの Stringf64に実装されているdefault()が返す値が入ってます。

default()を実装してみる

自分で好きなデフォルト値を定義したいときはstructdefault()を実装するとできます。

// #[derive(Default)]は外す
struct Product {
    name: String,
    price: f64,
}

// Defaultの実装
impl Default for Product {
    fn default() -> Self {
        Self {
            name: "Default name".to_string(),
            price: 999.0,
        }
    }
}

fn main() {
    let product= Product::default();
    // let product : Product = Default::default(); <- 同じ結果を返す。 
    println!("Name: {}, Price ${}", product.name, product.price);
}

これを実行すると

$ cargo run
Name: Default name, Price $999

と、設定した値が入ります。

enumにもDefaultを設定できる

enumにもdafaultを定義することができます。

#[derive(Default, Debug)] // Debugは`println`でログを出すために必要
enum Fruit {
    #[default]
    Apple,
    Banana,
    Orange,
}

fn main() {
    let fruit = Fruit::default();
    println!("{:?}", fruit);

    let banana = Fruit::Banana;
    println!("{:?}", banana);
}

実行結果は下記になります。

$ cargo run
Apple
Banana

どんな使われ方してる?

実際にどんな使われ方をしてるか気になったので調べてみました。

例1: actix-webのheadタイプ

Rustのweb libraryで有名な actix-webをみてみましょう。
Requestには RequestHeadがあります。

#[derive(Debug, Clone)]
pub struct RequestHead {
    pub method: Method,
    pub uri: Uri,
    pub version: Version,
    pub headers: HeaderMap,
    pub peer_addr: Option<net::SocketAddr>,
    flags: Flags,
}

impl Default for RequestHead {
    fn default() -> RequestHead {
        RequestHead {
            method: Method::default(),
            uri: Uri::default(),
            version: Version::HTTP_11,
            headers: HeaderMap::with_capacity(16),
            peer_addr: None,
            flags: Flags::empty(),
        }
    }
}

中身を見てみると各タイプのdefault値を返してますね。
versionに関してはHTTP_11がデフォルトで指定してるみたいです。

Reference:
https://github.com/actix/actix-web/blob/3200de3f34b21f65bf84d7b04ba118f03d808f02/actix-http/src/requests/head.rs

まとめ

Default値の定義を struct に実装することで、使用する時には実装されている default()を実行してあげるだけなので、簡単ですね。

公式ドキュメント

https://doc.rust-lang.org/std/default/trait.Default.html

pub trait Default: Sized {
    fn default() -> Self;
}

Discussion