// SPDX-License-Identifier: MIT /// This macro provides the same functionality as `core::mem::offset_of`, /// except that only one level of field access is supported. The declaration /// of the struct must be wrapped with `with_offsets! { }`. /// /// It is needed because `offset_of!` was only stabilized in Rust 1.77. #[cfg(not(has_offset_of))] #[macro_export] macro_rules! offset_of { ($Container:ty, $field:ident) => { <$Container>::OFFSET_TO__.$field }; } /// A wrapper for struct declarations, that allows using `offset_of!` in /// versions of Rust prior to 1.77 #[macro_export] macro_rules! with_offsets { // This method to generate field offset constants comes from: // // https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=10a22a9b8393abd7b541d8fc844bc0df // // used under MIT license with permission of Yandros aka Daniel Henry-Mantilla ( $(#[$struct_meta:meta])* $struct_vis:vis struct $StructName:ident { $( $(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_ty:ty ),* $(,)? } ) => ( #[cfg(not(has_offset_of))] const _: () = { struct StructOffsetsHelper(std::marker::PhantomData); const END_OF_PREV_FIELD: usize = 0; // populate StructOffsetsHelper with associated consts, // one for each field $crate::with_offsets! { @struct $StructName @names [ $($field_name)* ] @tys [ $($field_ty ,)*] } // now turn StructOffsetsHelper's consts into a single struct, // applying field visibility. This provides better error messages // than if offset_of! used StructOffsetsHelper:: directly. pub struct StructOffsets { $( $field_vis $field_name: usize, )* } impl $StructName { pub const OFFSET_TO__: StructOffsets = StructOffsets { $( $field_name: StructOffsetsHelper::<$StructName>::$field_name, )* }; } }; ); ( @struct $StructName:ident @names [] @tys [] ) => (); ( @struct $StructName:ident @names [$field_name:ident $($other_names:tt)*] @tys [$field_ty:ty , $($other_tys:tt)*] ) => ( #[allow(non_local_definitions)] #[allow(clippy::modulo_one)] impl StructOffsetsHelper<$StructName> { #[allow(nonstandard_style)] const $field_name: usize = { const ALIGN: usize = std::mem::align_of::<$field_ty>(); const TRAIL: usize = END_OF_PREV_FIELD % ALIGN; END_OF_PREV_FIELD + (if TRAIL == 0 { 0usize } else { ALIGN - TRAIL }) }; } const _: () = { const END_OF_PREV_FIELD: usize = StructOffsetsHelper::<$StructName>::$field_name + std::mem::size_of::<$field_ty>() ; $crate::with_offsets! { @struct $StructName @names [$($other_names)*] @tys [$($other_tys)*] } }; ); } #[cfg(test)] mod tests { use crate::offset_of; #[repr(C)] struct Foo { a: u16, b: u32, c: u64, d: u16, } #[repr(C)] struct Bar { pub a: u16, pub b: u64, c: Foo, d: u64, } crate::with_offsets! { #[repr(C)] struct Bar { pub a: u16, pub b: u64, c: Foo, d: u64, } } #[repr(C)] pub struct Baz { b: u32, a: u8, } crate::with_offsets! { #[repr(C)] pub struct Baz { b: u32, a: u8, } } #[test] fn test_offset_of() { const OFFSET_TO_C: usize = offset_of!(Bar, c); assert_eq!(offset_of!(Bar, a), 0); assert_eq!(offset_of!(Bar, b), 8); assert_eq!(OFFSET_TO_C, 16); assert_eq!(offset_of!(Bar, d), 40); assert_eq!(offset_of!(Baz, b), 0); assert_eq!(offset_of!(Baz, a), 4); } }