Impl Default for pointers?

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 Default::default().

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 MIN_VALUE or MAX_VALUE, and that it then propagates to unsigned integer for the sake of consistency.

But having bool : Default is already a weird thing to have; we could imagine this whole thread debating as to whether it should default to true or false.

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::default constructs 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 42.

Hence, whether <*const T as Default>::default() returns ptr::null::<T>(), 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 Default trait.

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 #[derive]ing 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.

3 Likes