Make self-reference by calculating fixed offset to the self's current address

Viewed 264

There is a blog post about Rust, where it is explained why Rust can't have self-referencing members.

There is an example with a slice storing offset and length instead of the pointer. I want to apply this to an object, where different parts need references to other parts.

Let's say the object looks like this:

struct Whole {
    struct Part0 { //members}
    struct Part1 {
        needs reference to part 0
    }
    struct Part2 { 
        needs reference to part 1 and part 0
    }
}

So, Whole can move around in memory (e.g., it can be stored in a vector). But in my scenario, Part1 and Part2 are always inside the Whole (and it is known at compile time). Also, Part1 always needs reference to the Whole it is currently part of. Thus Part 1 should be able to reference Part 0 by calculating its offset from Part 1's self.

I want to make a "reference" that stores offset of Part0 relative from Part1, so that Part1 can get a reference to Part0 by taking this offset from its 'self'.

3 Answers

You could just use Rc:

use std::rc::Rc;

struct Part0 {}

struct Part1 {
    at_0: Rc<Part0>,
}

struct Part2 {
    at_0: Rc<Part0>,
    at_1: Rc<Part1>,
}

struct Whole {
    at_0: Rc<Part0>,
    at_1: Rc<Part1>,
    at_2: Rc<Part2>,
}

fn main() {
    let at_0 = Rc::new(Part0 {});
    let at_1 = Rc::new(Part1 { at_0: at_0.clone() });
    let at_2 = Rc::new(Part2 {
        at_0: at_0.clone(),
        at_1: at_1.clone(),
    });
    let _ = Whole { at_0, at_1, at_2 };
}

Playground

But in my scenario, Part1 and Part2 are always inside the Whole (and it is known at compile time).

In that case, there's no reason for Part1 and Part2 to be structs of their own: their fields could instead be defined directly on Whole. Defining them as structs of their own might have made sense if that encapsulated some sub-behaviour, but the fact these parts need access to their siblings belies the suitability of such an approach.

If, for some reason, Part1 and Part2 must nevertheless remain as structs in their own right, then you could:

  1. Pass a reference to the relevant sibling into any method that requires it:

    impl Part1 {
        fn do_something(&self, part0: &Part0) {
            // do whatever with self and part0
        }
    }
    

and/or

  1. Implement relevant behaviours on Whole rather than the sub-part:

    impl Whole {
        fn do_something(&self) {
            // do whatever with self.part1 and self.part0
        }
    }
    

Of course, using unsafe code, it is nevertheless possible to discover the layout of a struct and access its members in the manner that you describe: I just don't see it being useful for the scenario described in this question. Nevertheless...

Ideally you'd want to embed knowledge of the layout of Whole's members directly into your compiled code. Unfortunately, because rustc does not directly provide means by which the layout of a struct's members can be discovered during compilation, the only way this could be done is by manually calculating the position of each member. However, as documented in The Default Representation in The Rust Reference (emphasis added):

Nominal types without a repr attribute have the default representation. Informally, this representation is also called the rust representation.

There are no guarantees of data layout made by this representation.

Consequently, if Whole is using Rust's default representation, it is impossible to determine the layout of its members during compilation (however, if Whole uses The C Representation, which guarantees a particular layout of a struct's members, one can manually calculate their positions at compile-time, which the repr_offset crate greatly simplifies); instead, the layout of Whole's members can be determined at runtime:

use std::ptr;

impl Whole {
    fn new() -> Self {
        let mut me = Self { ... };

        unsafe {
            let addr_of_part0 = ptr::addr_of!(me.part0).cast::<u8>();
            let addr_of_part1 = ptr::addr_of!(me.part1).cast::<u8>();
            let addr_of_part2 = ptr::addr_of!(me.part2).cast::<u8>();

            me.part1.offset_to_part0 = addr_of_part0.offset_from(addr_of_part1);
            me.part2.offset_to_part0 = addr_of_part0.offset_from(addr_of_part2);
            me.part2.offset_to_part1 = addr_of_part1.offset_from(addr_of_part2);
        }

        me
    }
}

And then one can do:

impl Part1 {
    unsafe fn part0(&self) -> &Part0 {
        let addr_of_self  = ptr::addr_of!(*self).cast::<u8>();
        let addr_of_part0 = addr_of_self.offset(self.offset_of_part0);
        &*addr_of_part0.cast()
    }
}

impl Part2 {
    unsafe fn part0(&self) -> &Part0 {
        let addr_of_self  = ptr::addr_of!(*self).cast::<u8>();
        let addr_of_part0 = addr_of_self.offset(self.offset_of_part0);
        &*addr_of_part0.cast()
    }

    unsafe fn part1(&self) -> &Part1 {
        let addr_of_self  = ptr::addr_of!(*self).cast::<u8>();
        let addr_of_part1 = addr_of_self.offset(self.offset_of_part1);
        &*addr_of_part1.cast()
    }
}

Note that these functions are unsafe because they rely on you, the programmer, only invoking them when you know that the respective offset_of_... fields are valid offsets to the relevant type that can be dereferenced into a shared pointer with the same lifetime as self. You likely will only ever be able to guarantee this when you are accessing self through an owning Whole, otherwise you could easily end up violating memory safety and triggering undefined behaviour—@Netwave's suggestion of using refcounted ownership is one thoroughly sensible way to avoid this risk.

you can use some black magic of unsafe, and encapsulate the logic private to your struct promise SAFETY internally

#[derive(Debug)]
struct Part0 {
    data: Vec<u8>,
}

struct Part1 {
    pdata0: NonNull<Part0>
}

struct Part2 {
    pdata0: NonNull<Part0>,
    pdata1: NonNull<Part1>,
}

struct Whole {
    p0: Part0,
    p1: Part1,
    p2: Part2,
}

impl Whole {
    fn new() -> Pin<Box<Self>> {
        // init Whole with NonNull
        let res = Self {
            p0: Part0 {
                data: vec![1, 2, 3],
            },
            p1: Part1 {
                pdata0: NonNull::dangling(),
            },
            p2: Part2 {
                pdata0: NonNull::dangling(),
                pdata1: NonNull::dangling(),
            },
        };

        // get boxed Whole
        let mut boxed = Box::pin(res);

        // get rawpointer of p0
        let ref0 = NonNull::from(&boxed.p0);

        // set p0 to p1.pdata0
        let mut_ref = (&mut boxed).as_mut();
        mut_ref.get_mut().p1 = Part1 {
            pdata0: ref0
        };

        let ref1 = NonNull::from(&boxed.p1);
        let mut_ref = (&mut boxed).as_mut();
        mut_ref.get_mut().p2 = Part2 {
            pdata0: ref0,
            pdata1: ref1,
        };

        boxed
    }

    fn use_p1(self: &Pin<Box<Self>>) {
        unsafe {println!("{:?}", self.p1.pdata0.as_ref())}
    }
}

fn main() {
    let mut h = Whole::new();
    h.use_p1();
}
Related