Raw ptr to DST, or, why isn't it `struct *mut T(usize) where T: Sized;`?


#1

I came across a usage of *mut [T] in some documentation I was reading, and I find myself questioning why *mut T doesn’t have T: Sized. It’s not like there’s anything useful that can be done with *mut [T]. You can:

  • Cast to *mut T and do the usual C pointer shenanigans. This cast is UB, since the layout of a ptr-to-slice is unspecified.
  • Go up through (*mut _)::as_ref() to get a &'unbounded [T] you can do something useful with (like [_]::into_raw_parts() it into a *mut T).
  • Check for nullity.

I question whether you should be winding up in a situation where you have a *mut Dst for Dst: !Sized… if I wanted a raw pointer to a slice I’d want to keep track of the length separately. I have even bigger trouble imagining a use case for *mut dyn Trait. If you need something like that there should probably be compiler support for breaking a trait object into a pointer + vtable beyond the shoddy mess that is std::raw::TraitObject.

Moreover, because they’re meant for FFI, I feel like there’s an expectation that *mut T always be an honest-to-God pointer. I’ve been writing Rust for a while now and I only recently learned there is no such guarantee!


#2

I don’t quite follow: are you talking strictly about when you get a *mut T from a *mut [T], or about *mut T in general? In the latter case I wouldn’t see why T would need to be Sized in all circumstances (?).


#3

I’m not convinced that *mut T for T unsized is useful, and that it is not actively harmful. If T = [U], the only well-behaved things you can do with a *mut T is check for nullity or cast up to a reference through as_ref(). The fact that *mut [U] is a well-formed type (with the same layout as &[U]) doesn’t seem like a good idea to me; *mut dyn Trait doubly so.


#4

Here’s a snippet from Cargo where we use * const str fat pointer to check interned string for equality quickly: https://github.com/rust-lang/cargo/blob/e511e15f4138a4914d6542875fdf10dc377d38f9/src/cargo/core/interning.rs#L28


#5

There are a few other things you can do with a raw pointer to a DST. You can compare or hash raw pointers, so you can determine if two of them point to the same memory, or use them as keys in a HashSet or BTreeSet where uniqueness is determined by address equality. (Yes, you could do this for slices by converting to a (*mut T, usize) pair, but using raw pointers allows libraries like by_address to have a single generic implementation that works for all types, sized and unsized.)

Cast to *mut T and do the usual C pointer shenanigans. This cast is UB, since the layout of a ptr-to-slice is unspecified.

The layout of *mut [T] is undefined, but the cast from *mut [T] to *mut T is, I believe, well-defined to discard the unsize info and return just the address (i.e., a pointer to the start of the slice). However, this doesn’t seem to be properly documented. RFC 401 only notes that such casts are valid, but not precisely what they do.


#6

I’ve used unsized pointers to transfer things over the FFI boundary between Rust programs (i.e., storing a pointer in C) via double-boxing; Box::into_raw(Box::new(Box::new(SomeTraitImplementation))) gives a normal pointer which C can copy/transfer and I can easily get at the internals later.


#7

I can name a few reasons:

  • Box<dyn Trait>
  • Rc<dyn Trait>
  • Arc<dyn Trait>

…all of which contain a *const dyn Trait.


#8

Thanks to everyone for your responses! Yeah, I definitely agree now that it’s useful to be able to work with *mut T generically; I had not thought of the by-addr comparison case. I also thought about SmartPtr<Dst> but sort of mentally swept it under the rug because of “mumble CoerceUnsized magic”. I don’t think that line of reasoning was sound.

A quick glance at the Book indicates that, when raw pointers are discussed, it’s not made explicit that not only is *mut T where T: !Sized allowed, but that it produces a fat pointer (mind, the latter should be a corollary of the first, but I think that a beginner learning about something raw pointers would want to make as few assumptions as possible about their properties) that is layout-compatible with the corresponding reference. I also couldn’t find anything to the effect in the Nomicon, either.