8

While raw pointers in Rust have the offset method, this only increments by the size of the pointer. How can I get access to the pointer in bytes?

Something like this in C:

var_offset = (typeof(var))((char *)(var) + offset);
4
  • Have you tried converting the pointer to a byte pointer? Commented Oct 28, 2016 at 18:02
  • tried but cast isn't allowed - casting `&MyStruct` as `*const u8` is invalid. Commented Oct 28, 2016 at 18:05
  • Glad to know I am not the only one who tried :D Commented Oct 28, 2016 at 18:06
  • 2
    If you want to convert a borrowed pointer to a raw byte pointer, you have to do two casts in a row: let x: &T; x as *const T as *const u8. Commented Oct 29, 2016 at 4:52

3 Answers 3

11

TL;DR: This answer invokes Undefined Behavior, according to RFC-2582.

In particular, references must be aligned and dereferencable, even when they are created and never used.

There are also discussions that field accesses themselves impose extra requirements not solved by the proposed &raw, due to usage of getelementptr inbounds, see offsetof woes at the bottom of the RFC.


From the answer I linked to your previous question:

macro_rules! offset_of {
    ($ty:ty, $field:ident) => {
        //  Undefined Behavior: dereferences a null pointer.
        //  Undefined Behavior: accesses field outside of valid memory area.
        unsafe { &(*(0 as *const $ty)).$field as *const _ as usize }
    }
}

fn main() {
    let p: *const Baz = 0x1248 as *const _;
    let p2: *const Foo = ((p as usize) - offset_of!(Foo, memberB)) as *const _;
    println!("{:p}", p2);
}

We can see on the computation of p2 that a pointer can be converted painless to an integer (usize here), on which arithmetic is performed, and then the result is cast back to a pointer.

isize and usize are the universal byte-sized pointer types :)


Were RFC-2582 to be accepted, this implementation of offset_of! is my best shot:

macro_rules! offset_of {
    ($ty:ty, $field:ident) => {
        unsafe {
            //  Create correctly sized storage.
            //
            //  Note: `let zeroed: $ty = ::std::mem::zeroed();` is incorrect,
            //        a zero pattern is not always a valid value.
            let buffer = ::std::mem::MaybeUninit::<$ty>::uninit();

            //  Create a Raw reference to the storage:
            //  - Alignment does not matter, though is correct here.
            //  - It safely refers to uninitialized storage.
            //
            //  Note: using `&raw const *(&buffer as *const _ as *const $ty)`
            //        is incorrect, it creates a temporary non-raw reference.
            let uninit: &raw const $ty = ::std::mem::transmute(&buffer);

            //  Create a Raw reference to the field:
            //  - Alignment does not matter, though is correct here.
            //  - It points within the memory area.
            //  - It safely refers to uninitialized storage.
            let field = &raw const uninit.$field;

            //  Compute the difference between pointers.
            (field as *const _ as usize) - (uninit as *const_ as usize)
        }
    }
}

I have commented each step with the reasons I believe they are sound, and why some alternatives are not -- something I encourage heavily in unsafe code -- and hopefully not missed anything.

Sign up to request clarification or add additional context in comments.

4 Comments

This is wrong; taking a reference of a value derived from a null pointer is undefined behavior: references must be aligned and dereferencable, even when they are created and never used. (From Kaz Wesley)
In that case, does a 'right' answer exist? Or is the only solution 'technically undefined', but 'working in practice'.
@ideasman42 that's correct, but there's support in nightly for doing it The Right Way (I'm hoping that the non-answer answer below is updated to show that).
@Shepmaster: Ah! Seems like the rules are shifting, or more accurately that uncertain code patterns are now ruled out. I've put a disclaimer while I think about it.
1

Thanks to @Matthieu M.'s answer, this can be done using pointer offsets, heres a reusable macro:

macro_rules! offset_of {
    ($ty:ty, $field:ident) => {
        &(*(0 as *const $ty)).$field as *const _ as usize
    }
}

macro_rules! check_type_pair {
    ($a:expr, $b:expr) => {
        if false {
            let _type_check = if false {$a} else {$b};
        }
    }
}

macro_rules! parent_of_mut {
    ($child:expr, $ty:ty, $field:ident) => {
        {
            check_type_pair!(&(*(0 as *const $ty)).$field, &$child);
            let offset = offset_of!($ty, $field);
            &mut *(((($child as *mut _) as usize) - offset) as *mut $ty)
        }
    }
}

macro_rules! parent_of {
    ($child:expr, $ty:ty, $field:ident) => {
        {
            check_type_pair!(&(*(0 as *const $ty)).$field, &$child);
            let offset = offset_of!($ty, $field);
            &*(((($child as *const _) as usize) - offset) as *const $ty)
        }
    }
}

This way, when we have a field in a struct, we can get the parent struct like this:

fn some_method(&self) {
    // Where 'self' is ParentStruct.field,
    // access ParentStruct instance.
    let parent = unsafe { parent_of!(self, ParentStruct, field) };
}

The macro check_type_pair helps avoid simple mistakes where self and ParentStruct.field aren't the same type. However its not foolproof when two different members in a struct have the same type.

Comments

1

As of Rust 1.62.0, the previous answers may produce the error reference to packed field is unaligned if the struct is #[repr(packed)].

The following offset_of! implementation uses addr_of! to avoid unaligned field references:

macro_rules! offset_of {
    ($type:ty, $field:tt) => ({
        let dummy = ::core::mem::MaybeUninit::<$type>::uninit();

        let dummy_ptr = dummy.as_ptr();
        let member_ptr = unsafe{ ::core::ptr::addr_of!((*dummy_ptr).$field) };
        
        member_ptr as usize - dummy_ptr as usize
    })
}

Source: https://internals.rust-lang.org/t/get-the-offset-of-a-field-from-the-base-of-a-struct/14163/4

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.