Getting a field to go at a specific offset

My dream is to have a proc macro that can expand something like this:

#[offset_ty]
struct Type {
    a: u64,
    b: u32,
    #[offset(0x460)] c: u32,
}

into this

#[repr(C)]
struct Type {
    a: u64,
    b: u32,
    _gap_0: [u8; 0x454],
    c: u32,  // this is at offset 0x460
}

The trouble is finding a way to generate that 0x454 in a const context. Basically, generating this number requires me to find a way to compute the offset at which _gap_0 will appear once inserted (i.e. what is the offset immediately after b?).

Some things I have tried:

  • For this simple example, we could try computing the size of a packed struct containing the fields up to that point:
#[repr(C)]
struct Type {
    a: u64,
    b: u32,
    _gap_0: [u8; {
        #[repr(C, packed)]
        struct PackedPrefix { a: u64, b: u32 }

        0x460 - std::mem::size_of::<PackedPrefix>()
    }],
    c: u32,  // this is at offset 0x460
}

But that fails if there's any padding in the middle of the struct (e.g. if we flip the types of a and b).

  • I thought of perhaps requiring that the struct has no padding, by performing a static assertion that the struct up to this point is of equal size with and without #[repr(packed)]. This would fail for the original example though.

  • const recently got a lot more powerful, however, I know that all current attempts to define an offset_of! macro are plagued by soundness issues, so surely there is no way to define one that works in a const context......is there.....?

So it occurred to me that since these types are used for FFI, there are only a small number of FFI-safe stdlib and primitive types that will ever appear in them, and so I tried going for a const offset_of! approach using a ConstDefault trait:

#[doc(hidden)]
#[macro_export]
macro_rules! thstruct__const_offset_of {
    ($ty:ty, $field:ident) => {{
        let x: $ty = $crate::ConstDefault::DEFAULT;
        let field = &x.$field as *const _ as *const u8;
        let base = &x as *const _ as *const u8;

        (unsafe { field.offset_from(base) }) as usize
    }};
}

/// A trait for producing a default value in a `const` context.
///
/// Used by the implementation of `#[offset]`.
pub trait ConstDefault: Sized {
    const DEFAULT: Self;
}

impl ConstDefault for usize { const DEFAULT: Self = 0; }
impl ConstDefault for isize { const DEFAULT: Self = 0; }
impl ConstDefault for u8 { const DEFAULT: Self = 0; }
impl ConstDefault for u16 { const DEFAULT: Self = 0; }
impl ConstDefault for u32 { const DEFAULT: Self = 0; }
impl ConstDefault for u64 { const DEFAULT: Self = 0; }
impl ConstDefault for i8 { const DEFAULT: Self = 0; }
impl ConstDefault for i16 { const DEFAULT: Self = 0; }
impl ConstDefault for i32 { const DEFAULT: Self = 0; }
impl ConstDefault for i64 { const DEFAULT: Self = 0; }
impl ConstDefault for f32 { const DEFAULT: Self = 0.0; }
impl ConstDefault for f64 { const DEFAULT: Self = 0.0; }
impl<T> ConstDefault for Option<T> { const DEFAULT: Self = None; }
macro_rules! impl_const_default_for_array {
    ($($n:literal)+) => {
        $(impl<T: ConstDefault> ConstDefault for [T; $n] {
            const DEFAULT: Self = [ConstDefault::DEFAULT; $n];
        })+
    };
}
impl_const_default_for_array!{
    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
    21 22 23 24 25 26 27 28 29 30 31 32
}

&raw references will hopefully eliminate the need for ConstDefault in the future. offset_from is currently unstable but seems to be riding the stabilization train at this very moment.

My attribute auto-derives ConstDefault. Of course, without const generics, [u8; 0x454] doesn't implement:ConstDefault, so the derived implementation has to explicitly provide values for these added "gap" fields. Bleh. It works.