Style question: Use of Default::default()

I have some structs with a bunch of HashMap, HashSet, and Vec that can be initialized as, for example, HashMap::new() or as Default::default(). Which is better style?

The former makes it easier to see the type of the variable when looking at the new() function. On the other hand, Arc::new(Mutex::new(HashMap::new()))) doesn't look as nice as Default::default().

What about usize, bool, and the like? Default::default() works for them, but it seems like overkill.

I'd actually like to derive Default for the whole thing and then set the few fields that need it, but there's no default for crossbeam Sender. I could wrap those fields in Option, but then I've got to unwrap to use them.

This will all be much easier after Centril's Default field values RFC... eventually.

In the meantime, I would advise against wrapping everything in Option just so you can derive Default. Also, personally I would absolutely avoid using Default for literals. Beyond that, IMHO I guess it's up to you.

One important difference here is that HashMap::new() is only defined for the default hasher, S = RandomState, whereas HashMap::default() works for any S: Default.

2 Likes

Depends. There's no one clear answer. I'd say that since Arc and Mutex are sort of low-level wrappers around the essence, which is the innermost wrapped type, I wouldn't bother hammering it out all and would probably just write Default::default() in this case. In Rust we don't explicitly write out all the types anyway thanks to type inference.

It's important for generic and generated code, though. I love myself a good unwrap_or_default() to work with primitives, as well as being able to #[derive(Default)] for a struct with fields of primitive types.

I just learned about the pattern Foo { a, b, ..Default::default() } and tried it on my example. Even though everything in Foo other than a and b implements Default, the compiler complains that Foo doesn't implement Default. Is there a reason the compiler can't accept this?

I managed to derive Default by wrapping a and b in Vec.

Foo { a: vec![a], b: vec![b], ..Default::default() }

I just need 3 extra characters, [0], to use them. The improved readability of my new() makes it worth doing, but Vec looks like the kludge it is. Is there another wrapper I can use?

1 Like

You could use Option<T> instead of Vec<T> and save an allocation.
Or you could wrap them each in an array of length 1, which would at least allow destructuring on the array.
But all of these just treat the symptoms of course.

I personally use the Default impl on the explicit types. Something like this:

Foo {
    a,
    b,
    some_hashset: HashSet::default(),
    some_boolean: bool::default(),
    some_arc_mutex_hashmap: Arc::default(),
}

It's not quite as concise as ..Default::default() syntax, and in some cases you still don't see the full type (Arc::default() example above). But I think it's an ok compromise for now, and it's far more readable than Default::default() on every field.

Option requires unwrap(), which is more characters than [0]. (It also needs as_ref() in my case.)
An array of length 1 doesn't work, because the type it holds must implement Default. At any rate, the allocation doesn't matter for performance. I'm only doing the allocation when creating an instance of the struct, which I use for a long time.

I would recommend just not using the ..Default::default() syntax if your struct truly doesn't have a reasonable default value.

1 Like

Using the Default impl on the explicit types does address the question of seeing the type when I create an instance. The problem, probably due to poor design on my part, is that I've got a few fields that get set explicitly and lot that get a default initialization, resulting in visual clutter. My IDE lets me collapse the struct, but I'd like to see only the fields that are being set to non-default values. I get that with ..Default::default().

Like, using a vector instead is just a ridiculous hack. It sounds like you're keeping it around for a while, so you probably only have one or maybe two places where you construct it. I really don't think it's that bad to just list the fields to avoid that ridiculous hack. You can make a constructor to move the initialization somewhere else if you feel it's too big.

1 Like

I hope my previous post explained why I like the ..Default::default syntax. I'm only using it in two places where the structs have a lot of fields with most of them initialized to default values.

You, @alice, seem to know about the compiler. Any idea why

Foo { a, b, ..Default::default() }

can't be made to work even with Foo doesn't implement Default as long as the remaining fields do?

It's because the syntax is turned into this:

let foo = Foo::default();
foo.a = a;
foo.b = b;

So it needs a default value for those fields. (at least that's what I think is going on)

Too bad. I understand the thinking because that's what I did manually before I learned about the ..Default::default() syntax.

Look, you should really just make a constructor:

let foo = Foo::new(a, b);
3 Likes

It's the visual clutter in Foo::new() that I'm trying to get rid of :smiley:

I think you will have to get used to that if you intend to use Rust much...

This happens conceptually at least because the struct update syntax accepts any valid instance of the struct. It’s not specific to Default. The mental model is “take this instance, and override these specific fields” not “call Default::default on every member of the struct.” The call to Defualt::default therefore desugars to ::default, which does not exist.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.