Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make mem::size_of_val and mem::align_of_val take raw pointers instead of reference #2017

Open
mikeyhew opened this issue Jun 2, 2017 · 13 comments
Labels
T-compiler Relevant to the compiler team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC.

Comments

@mikeyhew
Copy link

mikeyhew commented Jun 2, 2017

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;
@mikeyhew
Copy link
Author

mikeyhew commented Jun 2, 2017

Somewhat-relevant RFC: #1524 (Custom DSTs)
cc @nikomatsakis @ubsan

@le-jzr
Copy link

le-jzr commented Jun 2, 2017

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.

@mikeyhew
Copy link
Author

mikeyhew commented Jun 2, 2017

@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.

@le-jzr
Copy link

le-jzr commented Jun 2, 2017

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>().

@mikeyhew
Copy link
Author

mikeyhew commented Jun 2, 2017

You can't use size_of for ?Sized types - that's the whole point of size_of_val.

@ranma42
Copy link
Contributor

ranma42 commented Jun 2, 2017

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.

@le-jzr
Copy link

le-jzr commented Jun 2, 2017

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.

@SimonSapin
Copy link
Contributor

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.

@eddyb
Copy link
Member

eddyb commented Jun 5, 2017

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.

@eira-fransham
Copy link

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.

@mikeyhew
Copy link
Author

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.

@ranma42
Copy link
Contributor

ranma42 commented Oct 11, 2017

@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).

@mikeyhew
Copy link
Author

@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].

@Centril Centril added T-compiler Relevant to the compiler team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC. labels Dec 6, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-compiler Relevant to the compiler team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests

7 participants