Trait Default for generic type required but useless

Why, in the following code, B need the Default trait ?
Since this trait isn't actually required to create the Foo structure.

#[derive(Default)]
struct Foo<A, B> {
    mapA: HashMap<A, B>,
    mapB: HashMap<String, A>,
}

impl<B> Foo<u32, B> {
    // pub fn new_u32() -> Self {
    //     Foo::default() // the trait bound `B: std::default::Default` is not satisfied
    // }

    // pub fn new_u32() -> Self {
    //     Foo {
    //         ..Default::default() // the trait bound `B: std::default::Default` is not satisfied
    //     }
    // }

    pub fn new_u32() -> Self {
        Foo {
            mapA: HashMap::default(),
            mapB: HashMap::default(),
        }
    }
}

The Default derive macro adds : Default bounds to any generic parameters of the annotated type, even if they are not necessary for the generated implementation. To get the more general implementation, remove #[derive(Default)] and implement the trait manually.

impl<A, B> Default for Foo<A, B> {
    fn default() -> Self {
        Self {
            mapA: Default::default(),
            mapB: Default::default(),
        }
    }
}

The behavior of the derive macro is motivated by simplicity (avoiding doing any kind of analysis of the types of fields) and forward-compatibility (not making the bounds on the generated impl depend on private fields).

4 Likes

A useful technique for troubleshooting "funny" macro behaviour is to expand macros so you see what the compiler sees.

For simple examples, you can paste your Foo struct definition into the the playground and click Tools > Expand Macros. Otherwise check out the cargo-expand tool if you are wanting to expand macros in a larger crate.

Running it on Foo shows the compiler has added some prelude stuff at the top of the file...

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;

#[macro_use]
extern crate std;

... Then there is your code...

use std::collections::HashMap;

struct Foo<A, B> {
    mapA: HashMap<A, B>,
    mapB: HashMap<String, A>,
}

... And finally, we see the Default implementation generated by the compiler:

#[automatically_derived]
#[allow(unused_qualifications)]
impl <A: ::core::default::Default, B: ::core::default::Default>
 ::core::default::Default for Foo<A, B> {
    #[inline]
    fn default() -> Foo<A, B> {
        Foo{mapA: ::core::default::Default::default(),
            mapB: ::core::default::Default::default(),}
    }
}

And you can see it has unconditionally added A: Default and B: Default requirements.

1 Like

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.