Lokathor / bytemuck

A crate for mucking around with piles of bytes
https://docs.rs/bytemuck
Apache License 2.0
697 stars 77 forks source link

Deriving `NoUninit` (and therefore `Pod`) without `repr(packed)` by asserting all fields have the same alignment #263

Open ds84182 opened 3 weeks ago

ds84182 commented 3 weeks ago

An asserting const within a trait impl will trigger a late-monomorphization error. This can be used to directly check for uninit bytes.

This should be opt-in in the derive trait. For manual implementations a helper macro could provide the same field checks.

Please note that this implementation is was written in a couple of minutes, so it may have unforeseen issues.

Example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=8d97b800a96d2becd5d7b75fa52fb3fe

unsafe trait NoUninit {
    // Asserts fields are aligned, returns alignment
    const FIELDS_ALIGNED: usize;
}

unsafe impl NoUninit for u8 {
    const FIELDS_ALIGNED: usize = std::mem::align_of::<Self>();
}
unsafe impl NoUninit for u16 {
    const FIELDS_ALIGNED: usize = std::mem::align_of::<Self>();
}
unsafe impl<const N: usize, T: NoUninit> NoUninit for [T; N] {
    const FIELDS_ALIGNED: usize = <T as NoUninit>::FIELDS_ALIGNED;
}

#[repr(C)]
struct MaybeNoUninit<T> {
    byte: u8,
    payload: T,
}

unsafe impl<T: NoUninit> NoUninit for MaybeNoUninit<T> {
    const FIELDS_ALIGNED: usize = {
        let mut offset = 0;

        let field_align = <u8 as NoUninit>::FIELDS_ALIGNED;
        offset += std::mem::size_of::<u8>();
        offset = (offset + (field_align - 1)) & !(field_align - 1);

        let field_align = <T as NoUninit>::FIELDS_ALIGNED;
        assert!((offset & (field_align - 1)) == 0, "`payload` is misaligned, `byte` has a smaller alignment");
        offset += std::mem::size_of::<u8>();
        offset = (offset + (field_align - 1)) & !(field_align - 1);

        let struct_align = std::mem::align_of::<T>();
        assert!((offset & (struct_align - 1)) == 0, "struct has trailing padding bytes");
        struct_align
    };
}

fn main() {
    // These are fine:
    let _ = <MaybeNoUninit<u8> as NoUninit>::FIELDS_ALIGNED;
    let _ = <MaybeNoUninit<[u8; 2]> as NoUninit>::FIELDS_ALIGNED;
    let _ = <MaybeNoUninit<[[u8; 2]; 8]> as NoUninit>::FIELDS_ALIGNED;

    // Causes late-mono error:
    let _ = <MaybeNoUninit<u16> as NoUninit>::FIELDS_ALIGNED;
}