To anyone newly entering this thread: There is now an updated proposal to address the problem motivated in this opening post that works without changing the syntax.
Take a look at the following struct:
struct World {
tree: Tree,
lake: Lake,
mountain: Mountain,
}
A default implementation of that struct might look like this:
impl Default for World {
fn default() -> Self {
Self {
tree: Tree::grow(),
lake: Default::default(),
mountain: Default::default(),
}
}
}
Naturally, one would like to shorten the two default implementations in there. Luckily, the language offers a neat shorthand for this case:
impl Default for World {
fn default() -> Self {
Self {
tree: Tree::grow(),
..Default::default()
}
}
}
Now, the tree is initialised with its special method, and the rest by calling default.... Well, or so I thought up to today. The code with ..Default::default()
actually recurses endlessly.
I never read the documentation on the ..Default::default()
shorthand in detail, and to me it looked just like what I described before. It is actually not easy to google, as even with quotation marks, the phrase rust "..Default::default()"
does not make google understand that the latter is supposed to be a search term with, and in turn it yields no related results. So I asked myself, if I would be the only person with that misunderstanding.
Looking at this issue it appears that other people ran into the same problem. It has 4 linked issues (one of them from me) that follow the same pattern, and the authors mostly wish for a warning for the recursion I created above.
However I would like to go further and argue that this notation is not supposed to be part of the Rust language, because it is ambiguous. It is not clear what the ..Default::default()
refers to. It could be either the constructed type (Self
in this case), or it could be inserted for each field. I would even argue that the notation hints at it being inserted for each field.
- The notation is placed inside the struct, next to the fields.
- The
..
implies a continuation of what happened before, i.e. before fields are initialised one by one, so the notation would continue that. - The notation is placed as far as possible away from the type it actually relates to (
Self
in this case).
For these reasons, this notation is violating the goal of the Rust language to encourage code to be explicit in its intentions. Consider the following alternative examples of syntax for the same thing:
impl Default for World {
fn default() -> Self {
Default::default() but assign {
tree: Tree::grow(),
}
}
}
Now the call to Default::default()
is on the top level, so it looks unrelated to the fields, and more like it will actually be called on Self
. Going through our arguments from above:
- The notation is placed away from the fields, as it does not relate to them.
- No dots, nothing that indicates an "etc" anymore.
- The notation is now placed at the position of the struct type, hinting that it is related to that as opposed to some of its fields.
Further, we now have a keyword. If someone does not know what it does, they can google it.
So:
- Do you agree that the notation in itself is misleading if a reader does not know what it does?
- Do you agree that this is a problem?
- What do you think about the notation I proposed?