Discussion on offset_of!(..)

offset_of_val doesn’t need unsafe code but is (sadly) limited by the need to know the struct's name to be able to use pattern-matching to bypass Deref for field access:

macro_rules! offset_of_val {
    ($s:expr, $field:ident) => ({
        let s: &_ = $s;
        let o = (&s.f as *const _ as usize).wrapping_sub(s as *const _ as usize);
        // Triple check that we are within `*s` still.
        assert!((0..=$crate::mem::size_of_val(s)).contains(&o));
        o
    })
}

If we require specifying the struct name, we can recover the pattern-matching:

macro_rules! offset_of_val {
    ($s:expr, $Struct:path.$field:ident) => ({
        let s: &$Struct = $s;
        // Use pattern-matching to avoid accidentally going through Deref.
        let &$Struct { $field: ref f, .. } = s;
        let o = (f as *const _ as usize).wrapping_sub(s as *const _ as usize);
        // Triple check that we are within `u` still.
        assert!((0..=$crate::mem::size_of_val(&u)).contains(&o));
        o
    })
}

Then we can rewrite offset_of! to rely on offset_of_val!:

macro_rules! offset_of {
    ($Struct:path.$field:ident) => ({
        // Using a separate function to minimize unhygienic hazards
        // (e.g. unsafety of #[repr(packed)] field borrows).
        // Uncomment `const` when `const fn`s can juggle pointers.
        /*const*/ fn offset() -> usize {
            let u = $crate::mem::MaybeUninit::<$Struct>::uninitialized();
            offset_of_val!(unsafe { &*u.as_ptr() }, $Struct.$field)
        }
        offset()
    })
}
2 Likes