Rustのstd::default::Default
tech rust
Published: 2018-05-28

RustのstdにDefaultというのがある。
構造体のデフォルト値を決めることができる。
実用例からlibcoreでのどのような実装になっているかを見てみた。

traitはこんな感じ。

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

Example

  1. Defaultなし
    Defaultを利用しないとこんな感じでnew()を作るようになる。

    struct SomeOptions {
        foo: i32,
        bar: f32,
    }
    
    impl SomeOptions {
        fn new() -> Self {
            Self { foo: 0, bar: 0.0f32 }
        }
    }
    
    fn main() {
        let options: SomeOptions = SomeOptions::new();
    }
    
  2. Defaultを使う
    以下はドキュメントのコードです。
    #[derive]アトリビュートDefaultを使うことでDefault::default()SomeOptions::new()の代わりになってnew()を定義する必要がなくなりました!はい、便利。

    #[derive(Default)]
    struct SomeOptions {
        foo: i32,
        bar: f32,
    }
    
    fn main() {
        let options: SomeOptions = Default::default();
    }
    
  3. 一部の値だけデフォルトを用いる
    以下はドキュメントのコードです。
    一部の値だけデフォルトの値を使うともできる

    fn main() {
        let options = SomeOptions { foo: 42, ..Default::default() };
    }
    
  4. Defaultを実装する
    Defaultトレイトを自分で実装することももちろん可能。
    ドキュメントではEnumを用いたコードを紹介していた。

    struct SomeOptions {
        foo: i32,
        bar: f32,
    }
    
    impl Default for SomeOptions {
        fn default() -> Self {
            Self { foo: 0, bar: 0.0f32 }
        }
    }
    
    fn main() {
        let options: SomeOptions = Default::default();
    }
    
  5. unwrap_or_default()
    ResultOptionではunwrap_or_default()という関数が実装されている。
    Optionを用いた例

    #[derive(Default, Debug, PartialEq)]
    struct SomeOptions {
        foo: i32,
        bar: f32,
    }
    
    fn main() {
        let options: SomeOptions = None.unwrap_or_default();
        assert_eq!(options, Default::default());
    }
    
  6. 実用例
    gliumというOpenGLのラッパーではDrawParametersというでかい構造体がある。

    pub struct DrawParameters<'a> {
        pub depth: Depth,
        pub stencil: Stencil,
        pub blend: Blend,
        pub color_mask: (bool, bool, bool, bool),
        pub line_width: Option<f32>,
        pub point_size: Option<f32>,
        pub clip_planes_bitmask: u32,
        pub backface_culling: BackfaceCullingMode,
        pub polygon_mode: PolygonMode,
        pub multisampling: bool,
        pub dithering: bool,
        pub viewport: Option<Rect>,
        pub scissor: Option<Rect>,
        pub draw_primitives: bool,
        pub samples_passed_query: Option<SamplesQueryParam<'a>>,
        pub time_elapsed_query: Option<&'a TimeElapsedQuery>,
        pub primitives_generated_query: Option<&'a PrimitivesGeneratedQuery>,
        pub transform_feedback_primitives_written_query:
            Option<&'a TransformFeedbackPrimitivesWrittenQuery>,
        pub condition: Option<ConditionalRendering<'a>>,
        pub transform_feedback: Option<&'a TransformFeedbackSession<'a>>,
        pub smooth: Option<Smooth>,
        pub provoking_vertex: ProvokingVertex,
        pub primitive_bounding_box: (Range<f32>, Range<f32>, Range<f32>, Range<f32>),
        pub primitive_restart_index: bool,
    }
    

    さすがにこれを毎回書くのはしんどいのでDefaultトレイトが実装されている。 Defaultトレイトを実装することでDefaultから変更したい箇所だけ書けばよくなる。

    let params = glium::DrawParameters {
        point_size: Some(5.0),
        multisampling: false,
        ..Default::default()
        };
    

詳細

Rustのcoreライブラリではデフォルト値が決まっているものがある。 1.26時点では以下のようになっている。

typedefault
boolfalse
char‘\x00’
usize0
u80
u160
u320
u640
u1280
isize0
i80
i160
i320
i640
i1280
f320
f640

libcoreの実装

1.26のDefaultのはdefault_imp!というマクロを用いて実装されている。
実際の実装コードを見てもらうとがさっきの表の箇所が実装されているのがわかる。

macro_rules! default_impl {
    ($t:ty, $v:expr, $doc:tt) => {
        #[stable(feature = "rust1", since = "1.0.0")]
        impl Default for $t {
            #[inline]
            #[doc = $doc]
            fn default() -> $t { $v }
        }
    }
}

default_impl! { bool, false, "Returns the default value of `false`" }

これ以外にもtupleでも実装されていた。
他にもいっぱいあるみたいです。

追記(2018-05-29)

lo48576さんさんにこんなこともできるよと教えていただいたので追記。

その1

std::default::Defaultprelude経由で自動的にインポートされています。 Default::default()の代わりにSomeOptions::default()でできます。

Example1の改良

#[derive(Default)]
struct SomeOptions {
    foo: i32,
    bar: f32,
}

fn main() {
    let options = SomeOptions::default();
}

その2

#[derive(Default)]を用いた以下のようなコードはコンパイルが通りません。

OptionのデフォルトはNoneなのでNoDefault構造体Defaultを実装する必要がありません。
コンパイルを通するにはDefaultを実装します。

NG:

// Struct with no default impl.
struct NoDefault;

#[derive(Default)]
struct SomeOptions<Content> {
    foo: Option<Content>,
    bar: f32,
}

fn main() {
    // ERROR!
    // error[E0599]: no function or associated 
    // item named `default` found for type 
    // `SomeOptions<NoDefault>` in the 
    // current scope
    let _ = SomeOptions::<NoDefault>::default(); // NG
}

OK:

// Struct with no default impl.
struct NoDefault;

struct SomeOptions<Content> {
    foo: Option<Content>,
    bar: f32,
}

impl<Content> Default for SomeOptions<Content> {
    fn default() -> Self {
        Self {
            foo: Default::default(),
            bar: Default::default(),
        }
    }
}

fn main() {
    let _ = SomeOptions::<NoDefault>::default(); // OK
}

qnighyさんの記事lo48576さんの記事 の記事が参考になると思います。

Share on:
comments powered by Disqus