As discussed in this recent RFC, ptr::null and ptr::null_mut have a T: Sized bound. Is there a good reason we can’t remove this bound, making the bound T: ?Sized? I propose that we update these functions to take a T: ?Sized. All other functions/types in the ptr module that do not require size (eq, NonNull, etc) have a T: ?Sized bound.
Currently it is UB to have a null vtable pointer, it’s as if *mut Trait and &mut Trait / Box<Trait> all have the same safe &'static VtableForTrait pointer as the fat metadata.
This concept also came up in pr44932 when I wanted to unsize is_null(), and that was later re-added in pr46094 citing the discussion in rfcs#433. Checking for null is a simpler question than producing it though.
Not having a vtable pointer as @dtolnay explained is why simply adding T: ?Sized is rejected by the compiler:
error[E0606]: casting `usize` as `*const T` is invalid
--> a.rs:1:36
|
1 | fn null<T: ?Sized>() -> *const T { 0 as *const T }
| ^^^^^^^^^^^^^
error: aborting due to previous error
For more information about this error, try `rustc --explain E0606`.
Perhaps null() (and null_mut()) should be (based on) compiler intrinsics/builtins; the compiler can then fill in a valid vtbl ptr for a trait object and, more generally, fabricate a valid (but null) ptr to any T.
@vitalyd I don’t know how a compiler intrinsic/builtin would make a difference. The compiler cannot invent an implementation of the trait to use for null pointers, for example in:
Why not? AFAIK, there's no guarantee about a vtbl being the same even for the same impl (due to CGU differences) so I can't immediately see a reason why it couldn't fabricate something as it sees fit.
That would be UB since the ptr is null? So in some sense, it’s like a form of ! - it can pretend to be anything since all you can really do, safely, is check for nullness.
It would be quite unfortunate for that to be UB because it is 100% safe code. A ptr.type_name() is equivalent to (ptr.vtable.type_name)(ptr.data) and should work fine for a null data pointer. But as I commented previously, the UB comes from constructing a fat pointer containing a null vtable pointer.
Perhaps my understanding of method calls is wrong, but I was under the impression that you essentially dereference the self as part of the call - the this (so to speak) receiver has to be valid, even if you don't use any state from it in the callee. In that, you'd be deref'ing null data ptr, which would be UB.
But your comment implies that my understanding is wrong.
I understand the pointer is not actually dereferenced - my impression was that the ptr has to be valid as-if it was dereferenced.
In some OO language, a method call involves passing a this ptr as a hidden parameter; whether the parameter must be non-null or not is a lang design decision. I’m aware of arbitrary_self_types but I’m not sure what guarantees/restrictions it carries, such as in this case: is *const Self allowed to be null? Your responses indicate yes. Is that mentioned/documented somewhere? Or does it merely fall out of raw ptrs being allowed to be null and therefore arbitrary self types just “inherit” that?
Also, suppose arbitrary self types were out of the picture or required that self raw ptrs are not null (for sake of argument). Are there other reasons the compiler couldn’t fabricate a vtbl ptr?
At the end of the day, it seems odd that it’s so difficult to fabricate a null ptr, generically. The compiler, in a lot of ways, is likely best positioned to provide that facility (somehow - how exactly, I guess that’s what this thread is about at this point ).
Is there a reason that calling a method on a raw pointer (even if that method is safe) shouldn’t always be unsafe? If it were, then we could say that it was the caller’s responsibility to ensure that the pointer was valid. That would, in turn, allow a fat pointer with both the data and vtable pointers as NULL to be a valid (if not valid to dereference) pointer.
It’s one of the guarantees of Rust that a vtable will always be valid. Option<*const dyn Trait> is the same size as *const dyn Trait, so if you want something similar to a raw pointer with an invalid vtable, you can use Option::None.
EDIT: that is at least the case today, maybe we need an RFC to make this an official guarantee
I’m not sure what wins this gives us; slice pointers *const [T] can be assembled manually, and being able to produce sketchy null pointers to fancy DSTs (imagine something more delicate than a vtable!) is actually unsafe! Think of the reason why slice::from_raw_parts is unsafe; I think this is a rare situation where the metadata actually has a safe default (len: 0).