Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I set only some struct members to their default values?

I have struct B:

struct A {}

struct B {
    a: A,
    b: u32,
    c: i8,
    z: usize,
}

A does not have a Default implementation and no default that makes sense. The fields b..z all need to be initialised to the default (in this case, 0), and A will be initialised to some runtime value:

let b = B {
    a: some_a(),
    b: 0,
    c: 0,
    z: 0,
};

This is not ideal.

Even worse, if an aa were to be added to the struct, there would need to be another member added to the initialiser. Using ..Default::default() doesn't work because B doesn't have a Default implementation, because any As in the default will be too expensive to compute to simply throw away afterwards. Furthermore, I would need an extremely long struct initialiser in the Default implementation anyway.

Is there any way to set the remaining struct members to their default values (so that b would be set to the default of u32) without writing all the member names out?

Preferably, there should be minimum change required to struct initialisers when new members are added. This means this is not ideal:

let b = B {
    a: some_a(),
    b: Default::default(),
    c: Default::default(),
    z: Default::default(),
};
like image 908
me' Avatar asked Apr 27 '20 02:04

me'


1 Answers

If you have large structs, you can make your life easier by deriving a builder or a constructor function. There are a few crates that do this, and a popular one is derive_builder.

Using that crate, you can do something like:

use derive_builder::Builder;

// this derive will generate a struct called BBuilder
#[derive(Builder)]
struct B {
    a: A,
    #[builder(default = "0")]
    b: u32,
    #[builder(default = "0")]
    c: i8,
    /* ... */
    #[default(default = "0")]
    z: usize
}


fn main() {
    let a = A { ... };

    // all default values for fields b..z
    let b = BBuilder::default().a(a).build().unwrap();

    // or specify just some of the fields
    let b = BBuilder::default()
        .a(a)
        .c(42)
        .z(255)
        .build()
        .unwrap();
}

Adding new fields to B will not impact code that uses BBuilder, as long as the fields have defaults. The downside is that you get a runtime panic if you miss a required field, rather than a compilation error.


Another crate is derive-new, which is a bit simpler. You would use it like this:

use derive_new::new;

#[derive(new)]
struct B {
    a: A,
    #[new(default)] 
    b: u32,
    #[new(default)] 
    c: i8,
    /* ... */
    #[new(default)]
    z: usize
}

fn main() {
    let a = A { ... };

    // all default values for fields b..z
    let b = B::new(a);

    // To specify some of the default fields, you need to mutate
    let mut b = B::new(a);
    b.c = 42;
    b.z = 255;
}

This doesn't generate any extra structs, it just adds a new method, which takes all of the non-default arguments. It is a compilation error if you miss any non-default fields out.

like image 155
Peter Hall Avatar answered Nov 12 '22 20:11

Peter Hall