After some discussion with various Rust team members, this is the best that we could come up with as a pure macro solution:
/// Macro to get the offset of a struct field in bytes from the address of the
/// struct.
#[macro_export]
#[allow_internal_unstable]
macro_rules! offset_of {
($container:path, $field:ident) => {{
// Create an instance of the container and calculate the offset to its
// field. Although we are creating references to uninitialized data this
// is fine since we are not dereferencing them.
let val = $crate::__core::mem::MaybeUninit::<$container>::uninitialized();
let &$container { $field: ref f, .. } = &*val.as_ptr();
#[allow(unused_unsafe)]
let result = unsafe { (f as *const _ as *const u8).offset_from(val.as_ptr() as *const u8) };
result as isize
}};
}
This code should not have any UB. However it only supports full structs (no tuple structs, no tuples, no arrays) and only supports a single field (no field1.field2
).
Considering these restrictions, we feel that a built-in compiler feature is the best way forward. This will allow us to support all types of structs and nested fields, as well as producing a constant value that can be used with const-eval (just like mem::size_of
).
The remaining question is whether we want this as a keyword or as a macro-like construct. I believe that allowing an offsetof(Struct, field)
in an expression context will be confusing since offsetof
is not a real function despite looking like one.
This situation is exactly what the macro syntax was intended for. Consider the exclamation mark is println!(...)
which clearly indicates that println
is not a normal function. Therefore I believe that offset_of!(Struct, field)
is the best way to expose this feature.