impl<T> Default for NonNull<T>?

Is there any reason not to have

impl<T : Sized> Default for ::core::ptr::NonNull<T>
{
    #[inline]
    fn default () -> Self
    {
        Self::dangling()
    }
}

?

Documentation on ::core::ptr::NonNull::<_>::dangling()

Slight correction: impl<T: Sized> (although I don't know why you can't create a dangling pointer to a trait object?).

One reason to avoid this is the last paragraph in the docs, if someone is constructing the dangling pointer via Default they may not see this (and if is_default() is added they may erroneously try using it to detect dangling pointers):

Note that the pointer value may potentially represent a valid pointer to a T , which means this must not be used as a "not yet initialized" sentinel value. Types that lazily allocate must track initialization by some other means.

But I think anyone using NonNull probably needs to understand that outside of just being part of the documentation for this one method.

Well spoted, I fixed it.

Because you can't know its alignment I guess.

This is more an argument against is_default() thant against this, imho.

Creating an explicitly invalid non-null pointer “by default” (which often means “implicitly” as well, since several convenience APIs hide calls to default) seems like a Bad™ idea.

For integer types, the default value is zero, but for pointers, even the potentially null *const T and *mut T don’t implement Default as ptr::null(). If even this was deemed a footgun (and we know null pointers are), then a non-null pointer — which can’t be zero — defaulting to something invalid is even more so.

On a possibly related note, it doesn’t really fit the philosophy of a default value, either. If one has a pointer, one’s first thought is not that “Oh, this is surely just a dangling pointer pointing maybe nowhere, maybe somewhere unspecified”. It’s not what you want your pointers to be by default. It’s a special case that is useful for implementing data structures in an optimized manner, but surely the construction of an explicitly dangling pointer requires thought, care, and extra consideration. It’s not the very first tool one should reach for.

13 Likes

Fair enough, although it does boil down to people’s assumptions about Default::default().

I see it as a safe uninitialised() when not using Option (although there really is no excuse not to use Option with NonNull), specially with generic code.

For ptr::null() it was decided that we must always have a valid vtable pointer, so we can't fabricate an unsized null, and then dangling follows suit.

++

I argue that NonNull<_>::dangling should have been named NonNull<_>::wild, to make it extra-clear that this is a dangerous, random pointer. (There is an uncommon distinction of a dangling pointer being a pointer that was just freed, and a wild pointer being the equivalent of int* ptr; in C.

The current implementation just casts align_of to a pointer, though I'd argue that simply creating an uninitialized usize, aligning it (ptr & !(align_of - 1)), and casting it to a pointer would better illustrate this.

I’ll note that NonZeroU32 and friends don’t implement Default either, so it seems reasonable that NonNull<T> also doesn’t.

It looks like not everybody has the same expectations from Default. I will stick to what the official doc says:

If that is the case, it seems like there should be as many Default implementations as possible.

And this changes my position on the is_default() topic: definitely against now.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.