#![no_std]
#![cfg_attr(fieldoffset_assert_in_const_fn, feature(const_panic))]
#![allow(clippy::needless_lifetimes)]
#[cfg(all(test, fieldoffset_has_alloc))]
extern crate alloc;
use core::fmt;
use core::marker::PhantomData;
use core::mem;
use core::ops::Add;
use core::pin::Pin;
#[doc(hidden)]
pub extern crate memoffset as __memoffset;
#[repr(transparent)]
pub struct FieldOffset<T, U, PinFlag = NotPinned>(
usize,
);
PhantomData<(PhantomContra<T>, U, PinFlag)>,
);
struct PhantomContra<T>(fn(T));
pub enum AllowPin {}
pub enum NotPinned {}
impl<T, U> FieldOffset<T, U, NotPinned> {
#[cfg(fieldoffset_maybe_uninit)]
#[inline]
fn with_uninit_ptr<R, F: FnOnce(*const T) -> R>(f: F) -> R {
let uninit = mem::MaybeUninit::<T>::uninit();
f(uninit.as_ptr())
}
#[cfg(not(fieldoffset_maybe_uninit))]
#[inline]
fn with_uninit_ptr<R, F: FnOnce(*const T) -> R>(f: F) -> R {
f(mem::align_of::<T>() as *const T)
}
pub unsafe fn new<F: for<'a> FnOnce(*const T) -> *const U>(f: F) -> Self {
let offset = Self::with_uninit_ptr(|base_ptr| {
let field_ptr = f(base_ptr);
(field_ptr as usize).wrapping_sub(base_ptr as usize)
});
Self::new_from_offset(offset)
}
#[inline]
pub const unsafe fn new_from_offset(offset: usize) -> Self {
#[cfg(fieldoffset_assert_in_const_fn)]
assert!(offset + mem::size_of::<U>() <= mem::size_of::<T>());
let _ = mem::size_of::<T>() - (offset + mem::size_of::<U>());
FieldOffset(offset, PhantomData)
}
}
impl<T, U, PinFlag> FieldOffset<T, U, PinFlag> {
#[inline]
pub fn apply_ptr(self, x: *const T) -> *const U {
((x as usize) + self.0) as *const U
}
#[inline]
pub fn apply_ptr_mut(self, x: *mut T) -> *mut U {
((x as usize) + self.0) as *mut U
}
#[inline]
pub fn apply<'a>(self, x: &'a T) -> &'a U {
unsafe { &*self.apply_ptr(x) }
}
#[inline]
pub fn apply_mut<'a>(self, x: &'a mut T) -> &'a mut U {
unsafe { &mut *self.apply_ptr_mut(x) }
}
#[inline]
pub const fn get_byte_offset(self) -> usize {
self.0
}
#[inline]
pub unsafe fn unapply_ptr(self, x: *const U) -> *const T {
((x as usize) - self.0) as *const T
}
#[inline]
pub unsafe fn unapply_ptr_mut(self, x: *mut U) -> *mut T {
((x as usize) - self.0) as *mut T
}
#[inline]
pub unsafe fn unapply<'a>(self, x: &'a U) -> &'a T {
&*self.unapply_ptr(x)
}
#[inline]
pub unsafe fn unapply_mut<'a>(self, x: &'a mut U) -> &'a mut T {
&mut *self.unapply_ptr_mut(x)
}
pub const unsafe fn as_pinned_projection(self) -> FieldOffset<T, U, AllowPin> {
FieldOffset::new_from_offset_pinned(self.get_byte_offset())
}
pub const fn as_unpinned_projection(self) -> FieldOffset<T, U, NotPinned> {
unsafe { FieldOffset::new_from_offset(self.get_byte_offset()) }
}
}
impl<T, U> FieldOffset<T, U, AllowPin> {
#[inline]
pub const unsafe fn new_from_offset_pinned(offset: usize) -> Self {
FieldOffset(offset, PhantomData)
}
#[inline]
pub fn apply_pin<'a>(self, x: Pin<&'a T>) -> Pin<&'a U> {
unsafe { x.map_unchecked(|x| self.apply(x)) }
}
#[inline]
pub fn apply_pin_mut<'a>(self, x: Pin<&'a mut T>) -> Pin<&'a mut U> {
unsafe { x.map_unchecked_mut(|x| self.apply_mut(x)) }
}
}
impl<T, U> From<FieldOffset<T, U, AllowPin>> for FieldOffset<T, U, NotPinned> {
fn from(other: FieldOffset<T, U, AllowPin>) -> Self {
other.as_unpinned_projection()
}
}
impl<T, U, V> Add<FieldOffset<U, V>> for FieldOffset<T, U> {
type Output = FieldOffset<T, V>;
#[inline]
fn add(self, other: FieldOffset<U, V>) -> FieldOffset<T, V> {
FieldOffset(self.0 + other.0, PhantomData)
}
}
impl<T, U, V> Add<FieldOffset<U, V, AllowPin>> for FieldOffset<T, U, AllowPin> {
type Output = FieldOffset<T, V, AllowPin>;
#[inline]
fn add(self, other: FieldOffset<U, V, AllowPin>) -> FieldOffset<T, V, AllowPin> {
FieldOffset(self.0 + other.0, PhantomData)
}
}
impl<T, U, V> Add<FieldOffset<U, V>> for FieldOffset<T, U, AllowPin> {
type Output = FieldOffset<T, V>;
#[inline]
fn add(self, other: FieldOffset<U, V>) -> FieldOffset<T, V> {
FieldOffset(self.0 + other.0, PhantomData)
}
}
impl<T, U, V> Add<FieldOffset<U, V, AllowPin>> for FieldOffset<T, U> {
type Output = FieldOffset<T, V>;
#[inline]
fn add(self, other: FieldOffset<U, V, AllowPin>) -> FieldOffset<T, V> {
FieldOffset(self.0 + other.0, PhantomData)
}
}
impl<T, U, Flag> fmt::Debug for FieldOffset<T, U, Flag> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "FieldOffset({:#x})", self.0)
}
}
impl<T, U, Flag> Copy for FieldOffset<T, U, Flag> {}
impl<T, U, Flag> Clone for FieldOffset<T, U, Flag> {
fn clone(&self) -> Self {
*self
}
}
#[macro_export]
macro_rules! offset_of {
($t: path => $f: tt) => {{
#[allow(unused_unsafe)]
unsafe {
$crate::FieldOffset::<$t, _>::new(|x| {
$crate::__memoffset::raw_field!(x, $t, $f)
})
}
}};
($t: path => $f: ident: $($rest: tt)*) => {
offset_of!($t => $f) + offset_of!($($rest)*)
};
}
#[cfg(test)]
mod tests {
#[derive(Debug)]
struct Foo {
a: u32,
b: f64,
c: bool,
}
#[derive(Debug)]
struct Bar {
x: u32,
y: Foo,
}
#[derive(Debug)]
struct Tuple(i32, f64);
#[test]
fn test_simple() {
let foo_b = offset_of!(Foo => b);
let mut x = Foo {
a: 1,
b: 2.0,
c: false,
};
{
let y = foo_b.apply(&x);
assert!(*y == 2.0);
}
{
let y = foo_b.apply_mut(&mut x);
*y = 42.0;
}
assert!(x.b == 42.0);
}
#[test]
fn test_tuple() {
let tuple_1 = offset_of!(Tuple => 1);
let mut x = Tuple(1, 42.0);
{
let y = tuple_1.apply(&x);
assert!(*y == 42.0);
}
{
let y = tuple_1.apply_mut(&mut x);
*y = 5.0;
}
assert!(x.1 == 5.0);
}
#[test]
fn test_nested() {
let mut x = Bar {
x: 0,
y: Foo {
a: 1,
b: 2.0,
c: false,
},
};
let bar_y_b = offset_of!(Bar => y: Foo => b);
{
let y = bar_y_b.apply_mut(&mut x);
*y = 42.0;
}
assert!(x.y.b == 42.0);
}
struct Parameterized<T, U> {
x: T,
_y: U,
}
#[test]
fn test_type_parameter() {
let _ = offset_of!(Parameterized<Parameterized<bool, bool>, bool> => x: Parameterized<bool, bool> => x);
}
#[test]
fn test_const() {
use crate::FieldOffset;
#[repr(C)]
struct SomeStruct {
a: u8,
b: u32,
}
const CONST_FIELD_OFFSET: FieldOffset<SomeStruct, u32> =
unsafe { FieldOffset::new_from_offset(4) };
const CONST_VALUE: usize = CONST_FIELD_OFFSET.get_byte_offset();
assert_eq!(offset_of!(SomeStruct => b).get_byte_offset(), CONST_VALUE);
static STATIC_FIELD_OFFSET: FieldOffset<SomeStruct, u32> =
unsafe { FieldOffset::new_from_offset(4) };
assert_eq!(
offset_of!(SomeStruct => b).get_byte_offset(),
STATIC_FIELD_OFFSET.get_byte_offset()
);
}
#[cfg(fieldoffset_has_alloc)]
#[test]
fn test_pin() {
use alloc::boxed::Box;
use core::pin::Pin;
let foo_b = offset_of!(Foo => b);
let foo_b_pin = unsafe { foo_b.as_pinned_projection() };
let foo = Box::pin(Foo {
a: 21,
b: 22.0,
c: true,
});
let pb: Pin<&f64> = foo_b_pin.apply_pin(foo.as_ref());
assert!(*pb == 22.0);
let mut x = Box::pin(Bar {
x: 0,
y: Foo {
a: 1,
b: 52.0,
c: false,
},
});
let bar_y_b = offset_of!(Bar => y: Foo => b);
assert!(*bar_y_b.apply(&*x) == 52.0);
let bar_y_pin = unsafe { offset_of!(Bar => y).as_pinned_projection() };
*(bar_y_pin + foo_b_pin).apply_pin_mut(x.as_mut()) = 12.;
assert!(x.y.b == 12.0);
}
}