Move variable after creating a reference

Is it possible to move a variable and somehow still keep a reference to the variable which was created before moving? Or is there another solution for following problem?

Assume there is a Base struct, a DependentOnBase struct and Aggregate struct which contains both Base and DependentOnBase values. I'm trying to code it in following way:

struct Base {}

struct DependOnBase<'a> {
    base: &'a Base,
}

struct Aggregate<'a> {
    base: Base,
    depend_on_base: DependOnBase<'a>,
}

impl<'a> Aggregate<'a> {
    fn new() -> Aggregate<'a> {
        let base = Base {};
        let depend_on_base = DependOnBase { base: &base };

        Aggregate {
            base,
            depend_on_base,
        }
    }
} 

I get error: "cannot return value referencing local variable base"

How should I Implement the desired structures properly? Please note that I specifically want DependOnBase to have reference on Base not a clone.

That’s the typical case of a so-called “self-referencing”/“self-referential” datatype; the compiler doesn’t give you any support on something like this (i.e. unsafe code is necessary to do something like this). There are some crates out there that can help (so you don’t need to touch any unsafety yourself), e.g. the crate ouroboros.

Note that references refer to memory locations, and moving a value changes its location in memory, so an existing reference would be invalidated. However, a reference derived from another reference might be more stable, e.g. you can derive a &T from a &Box<T>, and when the Box<T> moves, the address for the &T remains stable. E.g. ouroboros addresses this issue this by wrapping every referenced field into an implicit Box, and then it only allows you to access the derived &T references.

Here’s some example code:

// In `Cargo.toml`:
// [dependencies]
// ouroboros = "0.15.0"

use ouroboros::self_referencing;

pub struct Base {
    field: i32,
}

pub struct DependOnBase<'a> {
    base: &'a Base,
}

#[self_referencing]
struct AggregateInner {
    base: Base,
    #[borrows(base)]
    #[covariant]
    depend_on_base: DependOnBase<'this>,
}

pub struct Aggregate(AggregateInner);

impl Aggregate {
    fn new() -> Self {
        Self(
            AggregateInnerBuilder {
                base: Base { field: 42 },
                depend_on_base_builder: |base| DependOnBase { base },
            }
            .build(),
        )
    }
    fn print_base_through_dependent(&self) {
        self.0.with(|this| {
            println!("{}", this.depend_on_base.base.field);
        })
    }
}

fn main() {
    let x = Aggregate::new();
    x.print_base_through_dependent();
}
5 Likes

If you put a value into Box it will reside on heap. So there is no safe solution to keep everything on stack?

No, there isn't. If you move a stack value, its address changes. Putting the value on the heap is the whole point – the implicit, owning indirection means that moving the box doesn't move its heap-allocated contents, only the pointer itself.

3 Likes

Moving that Box asserts its uniqueness and using that &T afterwards is undefined behaviour. It's why Box is inappropriate for these self referential structures.

It's better to into_raw the Box first, or to use alloc.

Good point, though that's just a small technical detail. In fact, AFAIK current versions of ouroboros do use an AliasableBox type (which contains a raw pointer) instead of ordinary Box for this reason.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.