It depends on the exact nature of that pointer, but you are right, that's something to be mindful of:
Given,
use ::std::{cell::Cell as Mut, os::raw::c_int, ptr};
#[derive(Debug, Default)]
#[repr(C)]
struct IntrusiveList {
next: Mut< Option<ptr::NonNull<IntrusiveList>> >,
prev: Mut< Option<ptr::NonNull<IntrusiveList>> >,
}
#[repr(C)]
struct Object {
data: c_int,
more_data: c_int,
list: List,
}
then
let obj1 = Object {
data: 0,
more_data: 0,
list: IntrusiveList::default(),
};
let list = &obj1.list;
let obj2 = Object {
data: 0,
more_data: 0,
list: IntrusiveList {
prev: Some(ptr::NonNull::from(list)).into(),
.. IntrusiveList::default()
},
};
list.next.set(Some(ptr::NonNull::from(&obj2.list))); // --+ Pointer
// | can only
// Get last data | be used
let at_last_data: *const c_int = { // | to access
let mut cur: ptr::NonNull<IntrusiveList> = // | the data
list.into() // | stored in
; // | `.list`
while let Some(next) = // |
unsafe { cur.as_ref() }.next.get() // |
{ // |
cur = next; // |
} // |
use ::memoffset::offset_of; // |
cur .as_ptr() // |
.cast::<u8>() // |
.wrapping_sub(offset_of!(Object, list)) // |
.wrapping_add(offset_of!(Object, data)) // |
.cast::<c_int>() // |
}; // |
unsafe { // UB, ptr is not allowed to access that memory |
println!("{}", *at_last_data); // <-------------------+
}
triggers UB.
That being said, if one makes sure a pointer with provenance over the whole Object
is used to create the raw pointer to the IntrusiveList
, then all is fine:
- list.next.set(Some(ptr::NonNull::from(&obj2.list)));
+ list.next.set(ptr::NonNull::new(
+ <*const _>::cast::<u8>(&obj2)
+ .wrapping_add(::memoffset::offset_of!(Object, list))
+ as *mut IntrusiveList
+ ));
FWIW, I think that by writing this post I have incidentally answered the question of field offsetting 