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.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.