If the offsetof keyword is resurrected (which I personally don’t want to do), I imagine it would be a new AST node. In which case, I don’t really see much difference in terms of implementation between a keyword or a special built-in macro. The only significant difference seems to be surface syntax, and I personally prefer the macro syntax.
Similarly, this could be (safely) implemented as a completely ordinary macro if there was a way to disable auto-Deref, but Rust presently has no way to do that. One alternative is to do the following (only works for sized types):
macro_rules! offset_of {
($ty:ty, $field:ident $(,)?) => ({
const fn offset() -> usize {
let uninit = $crate::mem::MaybeUninit<$ty>::uninitialized();
let ptr = uninit.as_ptr(); // requires as_ptr() to be a const fn;
let field_ptr = unsafe { &(*ptr).$field as *const _ };
let offset = (field_ptr as usize).wrapping_sub(ptr as usize);
// Requires const assert!.
assert!(offset == 0 || offset < $crate::mem::size_of::<$ty>());
return offset;
}
const OFFSET: usize = offset();
OFFSET
});
}
This doesn’t work for unsized types because you have to use size_of_val, which isn’t (and can’t be, I don’t think) a const fn. This doesn’t stop you going through Deref, but the assert ensures that you cannot escape the struct, and the fact that it’s a const should catch any issues at compile time (though with less-than-ideal error messages).
So far, my conclusions are that offset_of! should be a built-in magic macro with its own AST node. My arguments are:
- Implementing this as a pure (user-level) macro with existing Rust features requires crippling the
offset_of! macro in one way or another that makes it feel half-baked. I’m really don’t want to do that.
- Creating a new keyword and syntax would require a new AST node anyway. I don’t see any advantage (and it has the disadvantage of not being compatible with both Rust 2015 and 2018). Plus I just like the way the macro syntax looks at the user level.
- Changing Rust such that this could be implemented as a pure (user-level) macro (i.e., some way to prevent auto-
Deref) would require a new AST node (or similar) anyway. If such a feature is ever implemented, we can change offset_of! to be implemented using it, but I’d really like to avoid chaining the existence and stabilization of offset_of! to some other new language-level RFC and feature. We’ve got enough new language-level stuff being implemented; I don’t want to propose another one.
With that said, I’m going to try implementing this as a built-in magic macro. Even if it’s rejected it’ll be a good learning exercise for me.