How to work with `!Sized` types in Rust

What I was talking about with creating wide pointers

There are really no way to create a fat pointer to a user-defined structure, they can only be created by the compiler itself.

There's a way to create a wide pointer to a user-defined structure that is robust in the face of unspecified layouts: casting pointers with the same metadata.

Type of e U Cast performed by e as U
*T *V where V: Sized :star: Pointer to pointer cast

:star: or T and V are compatible unsized types, e.g., both slices, both the same trait object.

And in particular, for slices and DSTs using slices:

For slice types like [T] and [U], the raw pointer types *const [T], *mut [T], *const [U], and *mut [U] encode the number of elements in this slice. Casts between these raw pointer types preserve the number of elements. Note that, as a consequence, such casts do not necessarily preserve the size of the pointer's referent (e.g., casting *const [u16] to *const [u8] will result in a raw pointer which refers to an object of half the size of the original). The same holds for str and any compound type whose unsized tail is a slice type, such as struct Foo(i32, [u8]) or (u64, Foo).

See also.

Additionally, for slices, core gives us ptr::slice_from_raw_parts_mut, so we can create slice pointers in particular without having to maintain the invariants of references, on stable today.

How to make use of it

We want to first construct a *mut [MaybeUninit<T>] with the proper metadata (buffer capacity), but with the data address portion of the wide pointer pointing at the containing struct. Then you can perform a pointer cast to get a *mut VecView<T> with the appropriate metadata and data address.

Your Vec and VecView[1] have the same layout modulo the latter being unsized (alignment and field offsets). So the data address of the desired slice wide pointer can be the same as the original (data) pointer address (*mut Vec<T, N>).

slice_from_raw_parts_mut takes a *mut T to construct a *mut [T], and the metadata (buffer capacity) is N, so that would be:

        let data = self as *mut Self as *mut MaybeUninit<T>;
        let vv: *mut [MaybeUninit<T>] = slice_from_raw_parts_mut(data, N);

And then the pointer cast (and then borrowing as &mut):

        unsafe { &mut *(vv as *mut VecView<T>) }

And... that's it really.

[2]

However

You do miss out on the coercion goodness, so I'm guessing this is just a factual correction to your article and not a direction you want to pursue further.


  1. at this point in the presentation ↩︎

  2. The example in main illustrates that you need to keep VecView behind a privacy barrier or add unsafe to the constructor or something, since I could have just changed the length to 5 without writing any data. ↩︎

7 Likes