Dynamically Aligned Types?
Tags: rust, languagedesign
Published: 2021-01-11 03:54:35

At this point, you're probably aware of what a _dynamically sized type_, or _DST_, is. They're types like `[Type]` (_slices_) or `dyn Trait` (_trait object_) which don't have a singular known size at compile time. As a result, you can't have a binding with a DST type (e.g. `let x: [u8]`); every DST has to be behind an indirection (e.g. `let x: &[u8]`).

Currently in stable Rust, the only dynamically sized types are slices, trait objects, and structs that contain one of those two DSTs. For these, references are "fat": whereas `&u8` is physically just a `*const u8` pointer to a memory location, `&[u8]` is a `(*const u8, usize)` pair, where the second number is the length of the slice. `&dyn Trait` is roughly `(*const ?, &'static VTable)`, where the pointer is to the object, and the vtable reference holds all of the information about the trait impl. The size is then calculated (in the case of slices) or retrieved (in the case of vtables) when asked for (e.g. by `mem::size_of_val`).

But what about alignment (and `mem::align_of_val`)? For slices, it's still statically known; `[T]` has the same alignment as `T`. But for trait objects, alignment can vary:

```rust
let a: &dyn Sync = &0u16 as &u16;
let b: &dyn Sync = &0u64 as &u64;
dbg!(mem::align_of_val(a));
dbg!(mem::align_of_val(b));
```

```
[src/main.rs:6] mem::align_of_val(a) = 2
[src/main.rs:7] mem::align_of_val(b) = 8
```

Entering the picture, though, are _custom_ DSTs. There's no specific design that the lang team have endorsed, but there are [various RFCs](https://github.com/rust-lang/rfcs/pull/2594) already available, and we can think about what we would like from such a feature and what restrictions the language already places on it.

<small>I am not necessarily endorsing the linked RFC specifically, just using it as an example.</small>

The headlining feature for custom DST proposals is the ability to have "thin" DSTs: ones where the pointer is just a pointer, without any extra metadata. (Equivalently, with zero-sized metadata, probably `()`.) Optimally (using the above-linked RFC's terms) we could have something like

```rust
#[repr(C)]
pub struct Thin<T: ?Sized> {
    meta: <T as Pointee>::Metadata,
    data: T,
}
```

so you could have e.g. `&Thin<_>` to create a thin-reference supporting value for _any_ type, by just storing the pointer metadata alongside it.

While that specific definition won't ever work (as `&Thin<_>` inherit pointee metadata from its tail), if alignment is statically known, this is a fairly simple task to accomplish:

```rust
// Example only; uses imaginary made up APIs
impl<T: ?Sized> Deref for Thin<T> {
    type Target = T;
    fn deref(&self) -> &T {
        // Thin is repr(C), so meta is
        //   the first field without extra padding
        let meta: <T as Pointee>::Metadata =
            ptr::read(self as *const Self as *const _);
        // Calculate the offset to T
        let meta_layout = Layout::new::<T::Metadata>::new();
        let offset = meta_layout.size()
            + meta_layout.padding_needed_for(mem::align_of::<T>());
        // Assemble pointer to T
        ptr::from_raw_parts((self as *const u8).offset(offset), meta)
    }
}
```

but as we showed above, for `dyn Trait`, the alignment _isn't_ statically known! For `dyn Trait`, you need to have the metadata to look up the alignment. Should be simple enough:

```rust
        // Calculate the offset to T
        let meta_layout = Layout::new::<T::Metadata>::new();
        let data_align = Pointee::align_with_meta(meta);
        let offset = meta_layout.size()
            + meta_layout.padding_needed_for(data_align);
```

However, this runs into a problem: how do I get the alignment of a `Thin<_>`? If it's `Pointee::align_with_meta(Metadata)`, you can't: `Thin` doesn't have any pointer metadata, and the information you need to find the alignment is behind the pointer. So, change this to be `Pointee::align_of_val(&self)`, then? But that runs into a chicken-and-egg problem: when composing the value in a containing type, you need to know the alignment to know the offset of the value in order to get a pointer to it!

So, one of two things has to be true:

- For any type, it's possible to get its alignment with just the pointer metadata, independent of whether you have a valid pointer to an instance of the type, or
- The above is true for `T: ?Sized`, and a new `?` bound is added for unsized types that _actually_ can't be held by value / composed around, only behind an indirection.

The second option, tbh, sounds horrible. This "`?SimpleAlignment`" is _less_ motivated than `?DynSized` (for actually unknowable size, e.g. `extern type`), and even `?DynSized` is fighting tooth and nail (and losing) to avoid being added to the do-not-call list. It would be really nice to have an inline `Thin<dyn Trait>` so that composition can just work — `&[mut] Thin<dyn Trait>`, `Box<Thin<dyn Trait>>`, `Rc<Thin<dyn Trait>>` — but it's not worth the extra pain.

But it doesn't mean that all is lost, either! By taking a page from `Pin`'s playbook, we turn it around, and wrap the pointer rather than the pointee: `Thin<&[mut] dyn Trait>`, `Thin<Rc<dyn Trait>>`, etc. This doesn't _quite_ just work, since we do need to add extra data to the heap part, so it'd be `Thin<&Thinnable<dyn Trait>>`, which is a bit repetitive, but it shows that it's still possible if really desired.

By the way, I have a [crate that does exactly this](https://docs.rs/erasable/1/erasable/struct.Thin.html).

TL;DR takeaways:

- I'm a shill, use [my crates](https://github.com/CAD97/pointer-utils); numbers go up make me feel good.
- Requiring a pointer to get a pointee's alignment is a chicken-and-egg problem; you need the alignment to calculate the offset to produce the pointer. Any custom DST proposal should be `Metadata -> Alignment`.
- It's still possible to use thin pointers to types with dynamic alignment. You just have to un-thin them before actually doing interesting things with them.
- [`align_of_val_raw`](https://doc.rust-lang.org/core/mem/fn.align_of_val_raw.html) can be safe, or at least inherits the safety from `Pointee::align_with_meta`, as it's just a different accessor for the same data.