..Default::default() calls A::default()

I was surprised the following code forms infinite recursion. My guess is that in the new() function, ..Default::default() calls A::default() and overrides specific fields after.

My intuition was that ..Default::default() would simply call the default() for each of the struct members and be done. Right now I am a little flabbergasted, and wondering if there is some hidden rationale for this behavior.

Also, I think this is a case where the compiler/clippy could show a warning since this is an instant game over for a running application.

struct A {
    a: i32,
    b: i32,
}

impl A {
    fn new() -> Self {
        A {
            a: 0,
            ..Default::default()
        }
    }
}

impl Default for A {
    fn default() -> Self {
        Self::new()
    }
}

fn main() {
    println!("Start calling A::default()");
    let a = A::default();
    println!("End calling A::default()");
    println!("A(a:{},b:{})", a.a, a.b);
}

(Playground)

Output:

Start calling A::default()

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.82s
     Running `target/debug/playground`

thread 'main' has overflowed its stack
fatal runtime error: stack overflow

Trait::method is just syntax sugar for <SomeType as Trait>::method, where the concrete type will be inferred from context (if possible). There's no one concrete method on a trait that is different from the same trait method when implemented on a type. To put it differently, Default::default is not a thing without a type.

4 Likes

That's exactly what's going on: Defining and Instantiating Structs - The Rust Programming Language. The rationale is that you take the fields of another instance of A. That other instance does not have to be A::default().

1 Like

You seem to be expecting magic here. There is no magic (thankfully).

The Struct { fields, ..rest } syntax evaluates all fields and rest, and then uses the fields of rest to initialize the fields that weren't explicitly mentioned. This syntax is not specific to default, and it doesn't automatically call any methods or cherry-pick which fields to initialize. The rest expression is and must simply be a fully initialized, valid value of the same type as the struct it's being used in.

There's nothing else going on; Default::default() simply creates a value of the appropriate type by calling default() on the whole type; it doesn't behave differently just because it's being used in a FRU expression.

This compositionaly is an important property of a well-designed language – stuff doesn't change behavior randomly based on surrounding context.

7 Likes

This:

A { a: 0, ..Default::default() }

Is notionally this:

// I've added a field to highlight that `default` is only called once
// even when multiple fields are moved out of the result
{
    let A { b, c, .. } = Default::default();
    A { a: 0, b, c }
}

And more generally, the thing after .. is an expression:

let other_a: A = todo!();
A { a: 0, ..other_a }
// { let A { b, c, .. } = other_a; A { a: 0, b, c } }

This may seem odd, but it means you can do things like this:

pub struct S { a: String, b: String }
fn example(s: S) {
    let s2 = S { a: String::new(), ..s };
    let a = s.a;
}

Because while s.b was moved out of s, s.a and s itself were not moved. (In the case of ..Default::default(), you're moving or copying out of a temporary value.)

However, this also means that the functionality is not supported if the type has private fields or the non_exhaustive attribute, as those are intended to "allow" the type owner to extend the type with additional fields without it being a major breaking change. You also can't move out of non-Copy fields if the type implements Drop, because that would result in a partially uninitialized type, which isn't allowed for Drop types.

Which is to say, the notional behavior is not this:

{
    let mut temp = Default::default();
    temp.a = 0;
    temp
}

A shorthand for that would be nice, but doesn't exist today. (It's a breaking change to change the existing behavior, so a distinct syntax would be required.)

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.