rustborrow-checkerownershipobject-lifetime

How to solve "Returns a value referencing data owned by the current function" (Actual depenecies between structs)


I'm learning Rust (I'm a C++ dev) and I'm still getting used to the borrow checker. I have the following example (which is also on godbolt: https://godbolt.org/z/z873x9cPn):

struct Foo {
    value: i32,
}

struct Bar <'a> {
    foo: &'a mut Foo,
}

struct Parent <'a> {
    foo: Foo,
    bar: Bar<'a>,
}

impl <'a> Bar <'a> {
    fn new(foo: &'a mut Foo) -> Self {
        Self {
            foo
        }
    }
}

impl <'a> Parent <'a> {
    fn new() -> Self {
        let mut foo = Foo{ value: 2};
        let bar = Bar::new(&mut foo);

        Self {
            foo,
            bar,
        }
    } 
}

fn main () {
    let _parent = Parent::new();
}

But when trying to compile I get an error:

error[E0515]: cannot return value referencing local variable `foo`
  --> <source>:27:9
   |
25 |           let bar = Bar::new(&mut foo);
   |                              -------- `foo` is borrowed here
26 | 
27 | /         Self {
28 | |             foo,
29 | |             bar,
30 | |         }
   | |_________^ returns a value referencing data owned by the current function

I've been looking through other posts but they don't solve exactly this dependency. Also, I've been trying to figure what to do but found no solution.

What is the best solution?


Solution

  • If you implemented this the same way in C++ that you did in Rust, you'd have undefined behavior. Rust is actually saving you here.

    &mut foo creates a reference to the function-local foo, but then you move foo into a new instance of Parent, and so the reference isn't valid anymore. Rust catches this at compile time. You cannot move or drop a value while there is a reference to it.

    The simplest way around this from the perspective of complexity of implementation would be to use a reference-counted smart pointer (Rc) which will give Bar and Parent shared ownership of the Foo. (This is analogous to the C++ std::shared_ptr template, and has almost the same semantics.)

    However, this is complicated by the fact that you are giving away mutable references to the Foo. Rust does not allow you to obtain a mutable reference to the value held by an Rc unless there is only one Rc in existence at that point.

    You can get around this with Cell or RefCell, which provide interior mutability. RefCell is more flexible but has more runtime overhead, since it implements Rust's borrowing rules at runtime, meaning it can also panic if you use it incorrectly.

    This is what that would look like:

    use std::rc::Rc;
    use std::cell::RefCell;
    
    struct Foo {
        value: i32,
    }
    
    struct Bar {
        foo: Rc<RefCell<Foo>>,
    }
    
    struct Parent {
        foo: Rc<RefCell<Foo>>,
        bar: Bar,
    }
    
    impl Bar {
        fn new(foo: Rc<RefCell<Foo>>) -> Self {
            Self {
                foo
            }
        }
    }
    
    impl Parent {
        fn new() -> Self {
            let mut foo = Rc::new(RefCell::new(Foo { value: 2 }));
            let bar = Bar::new(foo.clone());
    
            Self {
                foo,
                bar,
            }
        } 
    }
    

    Since the struct you're trying to create is self-referential, the non-Rc way of handling this would involve pinning and unsafe. Basically you would have to create the Foo at a stable memory location before you take any references to it, and Pin<_> is required to ensure that the memory location of a value never changes. You likely would still need RefCell if you want the value to be mutable through both the Parent and the Bar.