Why is #[derive(Default)] not implicit?

I guess it may be for performance reasons?

Just because all my fields implement Default doesn't mean the combination of them should be the default for my struct. In some cases, it would even be invalid (e.g. NonZeroUsize consists of just a usize internally).

Also note that currently at least, you can't #[derive(Default)] and impl Default (i.e. derive doesn't check to see if you've explicitly implemented the trait).

6 Likes

And this becomes particularly true when one is doing things like wrapping FFI. You might just have a struct with a u16 in it, but that definitely doesn't necessarily mean that it's ok for anyone to create it in safe code with a zero in it -- it might be, but that u16 might also be a handle into some state in a different library that's not initialized yet so shouldn't be usable.

1 Like

It's a choice by the designers of the language that people should opt-in to Default.

1 Like

And, yet another level higher, Rust usually prefers explicit constructs over implicit ones (there are some major exceptions, such as local type inference), where seeing something written out helps understanding of the code. derive macros are actually a quite heavy-handed feature because they generate code, which usually at least implements a trait, but may define other items as well. It's good to see that written out, even if only as a gentle two-word reminder (since derive(Stuff) is not long or too annoying to type or read).

I have a Date struct. What should the default be?

Some might say 1970-01-01. Some might say 2000-01-01. Some might say their birthday. Some might say today (which is dynamic!).

Not every type has a default even make sense, even if it's technically possible to implement.

2 Likes

Ah, now I seem to see the problem. I wasn't aware that default can access private fields unlike the programmer. Which makes me wonder why it even exist in the first place. It doesn't seem to do more what functions/methods/constants do.

Seems it comes into handy when working with generics as trait bound, instead of having to write your own trait method for default values? I'm not sure where I would wanted to use it, though. Seems so "invisible". But I still suck with generics and traits in Rust. What do I know.

Welp, at least CLion can do that. Seems what I'm looking for is more of an IDE feature than language feature.

I have a Date struct. What should the default be?

Some might say 1970-01-01. Some might say 2000-01-01. Some might say their birthday. Some might say today (which is dynamic!).

Not every type has a default even make sense, even if it's technically possible to implement.

I don't think I understand the point of that question.

The standard default is obviously nothing. Like:
ints = 0
floats 0.0
bool = false

"Your" default is what ever you do inside the impl Default. Or in the new function, or a constant for that matter. New is best, I guess. The default, though. Seems it's only for heavy generics use.

1 Like

It's just convenience. You can just manually write out the same thing it does,

impl Default for Foo {
    fn default() -> Self {
        Foo {
            a: Default::default(),
            b: Default::default(),
            c: Default::default(),
            d: Default::default(),
        }
    }
}

It's also helpful not to have to read all the generated code -- it's much easier to see the derive and know "oh, it's doing the normal thing" than to read some IDE-emitted code and figure out that it's doing the normal thing. (See generated hashCode() methods in Java, for example.)

2 Likes

That's my point, though. If #[derive(Default)] were implicit, I'd have a Default impl that does not make sense for most cases. In reality, I don't implement Default on the Date struct for exactly this reason. "Nothing" doesn't mean much when it comes to dates.

2 Likes

Fun related fact: once a day, at 00:00:00.000, Python time objects are falsy. In the 90s, someone thought this would be consistent with 0 numeric types being falsy. By the time people realized it was a bad idea, it was too late.

20 Likes

How nice would it be having to write

get_username().or_else(|| Username {
    first_name: String::new(),
    middle_name: String::new(),
    last_name: String::new(),
})

every single time instead of get_username().unwrap_or_default(), wouldn't it?

Yeah, perhaps. But it can't do enums either currently with just an attribute. So, you will need a default impl Default for you enum, too, always.

The only real use I see is for generics inside an API. When you just can't access actual types, or even crates. So can't call new, or any other default method, or constant. Though, it's not like that there can, or should be only one default. There could be more. That's all init data in the end to start with. Such limits new functions/methods/constants don't have.

My goal is to keep the code simple, and the language use. So, I'm not a friend of this "thinking overhead". Like, do I use a default, or a new function, or a method, or a constant, or even a build pattern. Or the idiotic, do I use if, or if let, or match. Instead of just use match Because after you done choosing, and wrote the code, you actually may have to refactor, and then choose again, except, your have to move back and forth from a default to a new, or from a match or an if, or if let. You get the point. This is how features start to do more harm then good.

Well, defaults allow to be set partially. So Struct1{ field1: true, .. default }. I see this a lot in the Bevy game engine. Perhaps it's lighter to define than a builder pattern struct. But, here we go again with the choices that seem important while Linus of Linux says that there is nothing better than C. And he got a point, which is my point. It's called feature overload.

Just because you didn't want to write the _ => () for that Match of yours, you needed the If Let...

Meanwhile, it's all about good coding practice and style, yet most coders think blob code is readable and place their damn bracket on the same line...

/ end of rant

Another example would be Class vs Struct. Good thing that there are no classes in Rust. Less choosing overhead before you can even start writing code.

There's no "thinking overhead" in this. All of these choices are clearly codified in the API guidelines. You don't have to choose, you should observe and respect the following pattern instead:

  • The primary constructor should be called new, additional ones should be called with_*. They should be associated functions on the type, as opposed to free functions. Non-generic code should prefer them over default.
  • If your type has a parameterless new, and construction has no side effects and is deterministic, then you should implement Default and have it return the same value as the parameterless new associated function.
  • Generic code has no choice but to call Default::default since new functions aren't part of a trait.
5 Likes

That rant actually wasn't really about default, though. I can think of cases, even for performance reason at run time.

I had to remove the solution to my question, however, because, if you don't want, or rather can't have a default, well, then don't use it?! Any struct that got fields that need one another, or computation in order to be correct, so don't allow selective fields setting, they should have an attribute to simply block default use on them instead since they are "unsafe".

Well, fine. I don't know the statistics of what code got how many default compatible structs, and incompatible structs. It's just currently my own code seem to have more compatible than incompatible hence me wondering now why I would have to write that attribute all over again.

Um, yes? If default doesn't make sense for your type, then you shouldn't implement it. It's not that each and every single type ever has to be Default; it's just nice for a type to be so if possible. I honestly don't see what is wrong with that.

I can't make sense of what you are complaining against here or what feature request you are trying to make.

It's a choice by design, which I had explained in one of my previous replies to this topic. You also have to write out types on functions explicitly, even though they could technically be inferred, there exists algorithms for that, as well as languages (like most of those in the ML family) doing it. Rust doesn't do that kind of global inference, because it has the wrong trade-offs in the light of some of the design goals it picked.

If you don't like the design choices Rust has made, absolutely do feel free to use another language. Just going on the forum of a language and simply ranting about it is neither productive nor very polite. There are languages I don't like, and I simply refrain from using them. I don't go over to Java or PHP forums telling everyone how their favorite language sucks. It works for them, good for them.

If you want to ask genuine questions, go ahead, but if you can never be satisfied with any of the several answers pointing out what the reason behind a certain decision is, please do not keep complaining.

2 Likes

You really shouldn't lecture people when you yourself can't understand a simple point, that is in the very title of this topic, and in this paragraph. Read, again:

Also, the forum rules clearly state that you should question/attack people's ideas, and not their character.
So, I'm here, and I will question the living heck out of this language. Feel free to add to the conversation, or just shut up yourself.

There are languages I don't like, and I simply refrain from using them.

Funny, there are answers/questions/people that I don't like, and I refrain bothering with them....

But, hey, here I'm to explain a simple point to you: This not your room. You can't simple send me out because you don't like what I said. There is nothing "disrespectful" here. And you aren't even a moderator. Instead of going off topic, flag my posts, then we will see. Fun fact, no real moderator would bother because it's not me, it's you.

So, for the record. You went off topic, and you started the attacks. I end it now by ignoring you.

Edit: Stop flagging this post. It doesn't make you right. I'm afraid have to force the moderation to have a look at it.

You appear to be lobbying for a change to the language. That’s generally off-topic here, as this forum is for helping people use Rust as it currently is. The people that would need to sign off on such a change don’t generally frequent this forum, so there’s very little good that can come of it. If you want to influence the future direction of the language, I suggest you bring it up in the internals forum instead.

1 Like

One thing is that everyone have different views of Default. bool could be true by default if parts of a struct, making that implicit will create potential footguns. Similar to the Date @jhpratt mentioned, who knows if someone like their birthday as the default date?

While what I know rust tries hard to reduce footguns for developers, if making it explicit only requires one line of #[derive(Default)] I believe it is really worth it. The reasoning may be similar to the billion dollar mistake - null, all things could be None so why not have NULL?

One thing that we all seem to be dancing around, but not quite stating explicitly (unless I skimmed past it) is that default() is part of a type's public API. A lot of rust's design is about giving a developer control over what goes into an API, and what guarantees they make about semver compatibility. implementing default, whether manually or through #[derive(Default)] promises the user that a valid instance of the type can be constructed without having any additional information. That's a powerful promise which doesn't hold valid in a significant number of cases. For example

  • The type constrains the value to uphold memory safety (Box cannot be null).
  • The type constrains the value to provide other runtime guarantees (String must be valid UTF-8. A bson Object ID must be 12 bytes long).
  • The type requires some sort of manual configuration to be initialized (database connections might be a good example here.

Part of the reason rust code has a reputation for correctness is that rust tries very hard not to make incorrect assumptions.

In Go, you can instantiate any type with a struct literal, and it will interpolate the empty (default) value, even in private fields. The Go Way is to arrange things so that the empty value is a sensible default. The Rust Way is not to assume anything about the meaning of fields, and to disallow struct literals where any of the fields are not visible to the current code. As a developer, you must explicitly decide on a public interface for your types, and this includes deciding whether to implement the Default trait.

4 Likes