This would allow you to use null pointers to get the size of a value. As far as I know, these methods do not actually dereference the &T that is passed in. Here is a gist and a playground link that shows that they work just fine with null pointers, as long as you cast them to references so that it will type check.
I can't think of any reason that the pointer being passed in would actually need to be dereferenced. My understanding is that the pointer itself, along with its type, are all that are needed to give the size and alignment information.
Changing the signature to the following should be possible without breaking any backward compatibility, because &T is implicitly coerced to *const T.
fn size_of_val<T>(*const T) -> usize;
fn align_of_val<T>(*const T) -> usize;
Somewhat-relevant RFC: https://github.com/rust-lang/rfcs/pull/1524 (Custom DSTs)
cc @nikomatsakis @ubsan
You are saving vtable and reconstructing your reference with it, but it's the vtable that actually contains size information, so your example doesn't show anything. It works because you explicitly preserve the part that holds the size. Does null raw pointer have a vtable, or a slice length? I strongly doubt that.
@le-jzr Hmm... I didn't think about just calling ptr::null::<Foo>() directly. You're right, there's no way the vtable would point to anything relevant.
Still, even if it's not a good idea for those functions to take *const T, it would be nice if there was a guarantee in the documentation that the &T doesn't actually have to point to anything valid, so that you could get the size and alignment info using just the vtable/slice length/other metadata, as shown in the above gist. Unless there actually is some reason to dereference it, which I am not aware of.
But it needs to point to something valid, that's the whole point. You are querying the size of that "something valid". If you don't want to query a valid object, you can use mem::size_of::<T>().
You can't use size_of for ?Sized types - that's the whole point of size_of_val.
How is that supposed to work for array (slices)? It looks like in that case you are required yo use a fat pointer, otherwise it is impossible to know the size.
What does it mean to get size_of_val of unsized type without having a valid dereferencable pointer? That's meaningless concept, you either have a pointer you can dereference to a valid value, or you don't have any value at all and it's nonsense to query size of something that doesn't exist.
In std::ptr, pub const fn null<T>() -> *const T { 0 as *const T } does not have a ?Sized bound. It cannot be used to create a null raw slice or null raw trait object.
We haven't defined whether raw unsized pointers need to have valid metadata, I don't think.
@SimonSapin You can go further than that and just try 0 as *const Trait outside of a generic method - it's not allowed at all, although it's not clear what the intention behind that was.
If you want to allow a null pointer, just use mem::size_of, since you can only construct a null pointer for Sized values. mem::size_of_val for raw pointers is not a completely pointless usecase, but it would be for when you have a pointer that you don't know for sure points to valid memory rather than for null pointers.
I've been thinking about this again recently. As some of you have pointed out, ptr::null and null_mut have a Sized bound, so it is impossible to produce a pointer to a dynamically sized type (DST) that has invalid metadata (meaning length for slices, or vtable for trait objects) without unsafe code.
In fact, the only way to get a null pointer to a DST is to cast from a sized type, e.g. ptr::null() as *const SomeTrait. @ranma42 and @le-jzr does that answer your questions/concerns?
One concern that might come up, is the possibility that raw pointers to DSTs might one day possibly have a thin representation, where the pointer would have to be dereferenced to get at size/alignment info in the metadata. If thin pointers are implemented, though, I'm pretty sure they would have to be (or at least, really should be) a separate type from *const and *mut T. I've seen repr(thin) being floated around, but there's no reason that a type should have to choose between "fat" or "thin" when it can have both.
@mikeyhew currently the as cast you are suggesting is disallowed:
pub fn example() -> * const [i32] {
let x: * const u8 = std::ptr::null();
x as * const [i32]
}
fails with error[E0607]: cannot cast thin pointer *const u8 to fat pointer *const [i32].
Regarding my concerns, it is still not clear to me how to provide the number of elements of the slice when performing the cast (unless you manually and unsafely construct the pointer/length pair and transmute it to a slice).
@ranma42 you need to cast from a type that implements Unsize<[i32]>, such as a fixed size array:
let slice = ptr::null::<[i32; 3]>() as *const [i32];
The way it works is, the compiler provides the implementation of Unsize<[i32]> for [i32; 3], and uses that implementation when doing the coercion from *const [i32; 3] to *const [i32].
Most helpful comment
What does it mean to get
size_of_valof unsized type without having a valid dereferencable pointer? That's meaningless concept, you either have a pointer you can dereference to a valid value, or you don't have any value at all and it's nonsense to query size of something that doesn't exist.