This whole topic, as well as the other referenced one regarding
NonNull, shows the issue with
Default: the very name seems to imply that there is a canonical value for a type, which is the one returned by
When thinking about it, this is not the case for many types. I guess that with signed integers, the argument of “symmetry” (if we dismiss that
MIN + MAX ≠ 0) makes
0 more special than
MAX_VALUE, and that it then propagates to unsigned integer for the sake of consistency.
bool : Default is already a weird thing to have; we could imagine this whole thread debating as to whether it should default to
The solution for this “debate” is in the official description of the trait, from the docs:
So, the idea becomes the following:
Problem of semantics
Default::defaultconstructs an arbitrary valid value of type
Self, but the actual choice is arbitrary.
That is, it should be perfectly fine for
<i32 as Default>::default() to return
<*const T as Default>::default() returns
1 as *const T, or
mem::align_of::<T>() as *const T should not matter (yes, even an unaligned address should be fine, given the real semantics of
Default). Imho we should try to choose the one more likely to cause a memory violation when dereferenced, and for such thing
NULL seems like the established consensus.
However, if such choice is so hard to make, then surely the problem lies within the
A new equivalent trait but for its naming could be made, something like
Arbitrary::arbitrary(), only “special-cased” in its construction for
Option<T>, where it would be guaranteed to give
None. For anything else, the value it creates should never be relied upon (it would even be allowed to have an implementation where it differs from call to call), except for the fact that it is not only valid, but safe (i.e., this would not be
mem::uninitialized, although for integer types
mem::uninitialized would be a valid implementation!).
For instance, testing whether something equals
Default::default(), as suggested in this other thread, would become testing whether something equals
Arbitrary::arbitrary(), which shows how absurd the very test is (for such use cases,
Option<T> or a manually crafted special discriminant should be chosen; in case of pointer types, this is of course
Option<NonNull<T>> instead of
*const T, and instead of
*mut T we should use … err, there is no
NonNullMut!? That’s for another topic, however).
As some have stated, relying on
mem::uninitialized/zeroed or even
mem::MaybeUninit just because
Default was not possible and implementing it without helper crates is cumbersome (not everybody knows of / uses something like
::derivative) is worse than having
<*const T as Arbitrary>::arbitrary() exist.