Implementing partial default trait on structs

Hi everyone,

Stuck with another one, if someone can share a better way please.

struct SomeHolder {
    len: u32,
    type: String,
    common_holder: Box<CommonHolder>
}

impl Default for SomeHolder {
    fn default() -> Self {
         SomeHolder{type: "UNKNOWN".to_string(), 
                     ..Default::default()}
    }
}

warning: function cannot return without recursing
  --> src/main.rs:34:5
   |
34 |     fn default() -> Self {
   |     ^^^^^^^^^^^^^^^^^^^^ cannot return without recursing
35 |         BoxCommon{box_type: "XXXX".to_string(), 
36 |                   ..Default::default()}
   |                     ------------------ recursive call site
   |
   = help: a `loop` may express intention better if this is on purpose
   = note: `#[warn(unconditional_recursion)]` on by default

What is the usual way of creating default values for structs? (Searching on this topic did give some results, but I have notcome across hierarchical Default chaining yet)

Initially, I thought new was in an implicit trait since majority of the standard library types seem to have implemented new. I guess not, and I had to resort to Default.

I can go with my own new for all the structs that I am defining and chain the new methods between base struct and specific structs : however, I am losing out on “And the rest” pattern binding.
I can also see how this is going to become a problem when adding/modifying struct members if I am dealing with few levels of nested and/or hierarchical structs - I'd have to revisit every single new to take care of the modification/addition.

Is there a simpler way?

Many thanks!

The ..Default::default() syntax cannot help here. Contrary to common misconception, it does not mean “fill all remaining fields with default values”. Instead it means “create one default value for the whole SomeHolder struct, then fill all remaining fields here by taking the field values from that struct”.

Solutions that don’t involve manually listing all fields like len: Default::default(), common_holder: Default::default() can be made to work with macros.

For example the crate smart-default seems suitable.

use smart_default::SmartDefault;

#[derive(SmartDefault)]
struct SomeHolder {
    len: u32,
    #[default = "UNKNOWN"]
    type_: String,
    common_holder: Box<CommonHolder>
}

(Rust Explorer)

4 Likes

A written-out example of the manual field approach:

#[derive(Default)]
struct CommonHolder;

struct SomeHolder {
    len: u32,
    r#type: String,
    common_holder: Box<CommonHolder>
}

impl Default for SomeHolder {
    fn default() -> Self {
        Self {
            r#type: "UNKNOWN".to_string(),
            len: <_>::default(),
            common_holder: <_>::default(),
        }
    }
}

But as you said, if you use this approach and later add fields, you'll have to edit the implementation.

Another alternative is to use a custom type[1] or a new type[2] for fields like r#type, in which case, you can give them a custom Default implementation and then derive Default on SomeHolder.


Some more words about struct update syntax.

Note that this:

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn example(user1: User) {
    // This part is the struct update syntax
    let user2 = User {
        email: String::from("another@example.com"),
        active: true,
        ..user1
    };
    
    let email = user1.email;
}

Does not act like this:

    let user2 = {
        let mut tmp = user1;
        tmp.email = String::from("another@example.com");
        tmp.active = true;
        tmp
    };

Instead it acts like this:

    let user2 = User {
        email: String::from("another@example.com"),
        active: true,
        username: user1.username,
        sign_in_count: user1.sign_in_count,
    };

And that is why you can use the unmoved fields of user1.

    let email = user1.email;

Because the thing after .. can be any expression that has the same type as the struct, a common idiom is:

    let user2 = User {
        email: String::from("another@example.com"),
        active: true,
        ..Default::default(),
    };

But as @steffahn noted, this calls <User as Default>::default(), and constructs an entire User to pull fields out of. (Then the rest of the temporary value drops). That is, Default::default is not magical here, and it does not act field-by-field. It's just an expression that must produce the outer type, like any other. That's why the call was self-recursive in your OP.


  1. that is more strictly typed, like an enum of possible types or something ↩︎

  2. struct Ty(String); ↩︎

3 Likes

Thank you @steffahn.
This smart-defaults crate is very good, and seems to just work! It does solve the problems of default chaining in a hierarchical structs definitions.

thanks for explanation @quinedot . I may have understood that I am a brat expecting too much from a low level language - and how the rust community is making things easier by literally writing effective macros for everyday use. Rust Macros are powerful just like in C, and I understand I have to spend some time in coming up with own macros for repetitive stuff.

Thank both for your inputs!

FYI I stumbled across this another more generic crate called derive_builder that offers other features.