Cannot call Default::default() on map of non-default value type

I am trying to create a types that has HashMap as one of its fields:

use std::{
    hash::Hash,
    collections::HashMap,
    path::Path,
};

#[derive(Debug, Default, Eq, PartialEq)]
struct Container<Key: Hash + Eq, Value> {
    container: HashMap<Key, Value>,
}

fn main() {
    let x: Container<String, &Path> = Default::default();
    dbg!(&x);
}

playground

which should be reasonable since a default map is a map that holds no value, meaning its value type shouldn't have to implement Default trait. But the compiler doesn't let me.

My questions:

  • Is this an oversight on Rust part, a bug, or am I missing something?
  • How do I fix this?

There's nothing wrong with HashMap. If you read its docs, you can see that it implements Default even when the key and value types don't.

The problem is that you are deriving the default impl for your own, generic type, which automatically puts the Default trait bound on the type parameters (because that's the more common case, and it can't know you don't need it).

So, impl Default manually without adding the bounds to the key and value type. You'll see that HashMap::default() will work in that impl.

Yes, this is a bug. See this issue, which was closed in favor of this more general issue. You'll have to implement Default manually for now unfortunately.

1 Like

Implementing Default manually is a chore, but I guess I have no choice then.

Why doesn't #[derive(Default)] generate this perfectly valid code below?

impl<Key: Eq + Hash, Value> Default for Container<Key, Value>
  where HashMap<Key, Value>: Default,
{
   /* ... */
}
1 Like

Anyway, I have found a solution:

For the first question, refer to this answer.

For the second question, Default macro is flawed and dumb, so I just need to find a smarter macro: SmartDefault.

Because it has made an intentional choice to make what it does not depend on implementation details of the type (its possibly-private fields), only on the exposed generic types.

That way changes like https://github.com/rust-lang/rust/pull/67642 or https://github.com/rust-lang/rust/pull/39287 don't affect the bounds on types using the derive macros.

2 Likes

Furthermore, in addition to what @scottmcm wrote, it's also not quite possible to account for every possible such implementation detail in fields without proper type checking, which, however, macros don't have access to.

I'm sorry but I don't really buy that argument. It is literally five lines:

impl<Key, Value> Default for Container<Key, Value> {
    fn default() -> Self {
        Container { container: HashMap::new() }
    }
}

It is only 5 lines because the struct has only 1 field.