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

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!

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

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.

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

2 Likes

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.

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.

1 Like

I can name a few reasons:

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

…all of which contain a *const dyn Trait.

4 Likes

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.

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