Request: IsZero trait and #[derive(IsZero)]

Not in Rust. Not setting the value of a variable or memory location leaves it in an undefined state, and trying to use it without initializing it first is Undefined Behavior and can lead to problems in seemingly unrelated code. Rust prevents use of uninitialized values and uninitialized memory, but has a few escape hatches if you are really, really sure that you can remember to initialize it manually first.

In addition, it has a few things to make initializing easier, one of them the "I just need a value" trait Default.

3 Likes

You are getting off-topic. I said that in the context of "my use case" (which is serializing values), not in general. The "something" I referred to was a field in a JSON file, not a declared variable in a .rs file.

Note that I asked early on if there were any use cases outside of serde. A limited use case combined with the ability to implement this in userland doesn't convince me that this is needed in std. I'd much rather see it done in a crate, which could then be pulled into std if deemed appropriate. This has happened with a large number of APIs in the past.

2 Likes

Rust does have IsZero internally in https://github.com/rust-lang/rust/blob/665190b34609c5204a3bd57c571c1dff4d8bc5f2/src/liballoc/vec.rs#L1846-L1914 with specialization, but not sure if it would be useful to make it public.

But so far I never seen any #[derive(IsZero)].

1 Like

Funnily I had exactly the same problem 2 weeks ago, and ended up implementing the IsZero trait (with that exact name). IsDefault couldn't work because we can't implement or derive Default for foreign traits (in my case std::net::Ipv4Addr).
A generalized IsDefault would be perfect (because what we really want is something that won't be serialized if it can be created via #[serde(default)] when missing), but it would need to be in std and be implemented by nearly all types.

I don't understand this argument. Putting a trait in std isn't going to make it a non-foreign trait, so you can't implement it any better than you can Default, and if it is to support serde, why not put it in serde? Beside that, I don't think the phrase "nearly all types" is as meaningful as you seem to assume. Do you mean nearly all types in std? What criteria would you use that just wouldn't be exactly as applicable to Default?

1 Like
pub trait IsDefault: Default + Eq { // or PartialEq if you prefer
    fn is_default(&self) -> bool;
}

impl<T: Default + Eq> IsDefault for T {
    fn is_default(&self) -> bool {
        self == T::default()
    }
}

You could then use crate::IsDefault as necessary, and have IsDefault::is_default passed to serde. I'm pretty sure this works exactly as expected, and I just typed it off-hand. Obviously it doesn't work for types that don't implement Default, but that's a separate issue imo.

Of course, it might be feasible to add this to the Default trait, given the Eq bound is satisfied. I personally wouldn't be opposed to that.

1 Like

Ok my bad, I'll explain it better:

pub trait IsDefault: Default + Eq { // or PartialEq if you prefer
    fn is_default(&self) -> bool;
}

This would be perfect, or even just a free is_default<T>(t: T) where T: Default + Eq function, but for this to work Default needs to be implemented for "nearly all types" of std.

Having a IsDefault trait has already been discussed, and as a TL,DR: Default is a not a trait for null-ness or neutrality, but for having the ability to create some arbitrary inhabitant of the given type.

Source
  • emphasis mine

Although concrete types may express additional invariants w.r.t. the actual instance created by Default::default(), there is no part of the contract specifying it. The values could change in between invocations, and the value may very well be "non-empty".

Having a IsDefault trait would thus contradict this purposely simple trait definition.


That being said, nothing forbids you from having your own IsDefault trait within your crate, or to publish a dedicated crate for this very functionality.

3 Likes

Which is exactly the point of #[serde(default, skip_serializing_if = "...")]: we just want to avoid serializing a value because we know it'll be recreated if missing (and in my case because I have other constraints where I really have to avoid serializing them).

1 Like

Have you considered submitting PRs on the Rust repo adding Default implementations? They would then be able to be discussed one at a time, which is what would be necessary.

1 Like

I'm afraid it will trigger my fear of rejection but yes you're right, I should have thought of it.

You don't need the (Partial)Eq superbound for Option<T>. (Option<T>: PartialEqT: PartialEq, but Option::is_none is defined for all T.)

I just quickly typed am example of what might work. Of course it might need to be adjusted to cover all use cases — I never claimed otherwise.