Search code examples
rustreferencereturn-valueborrow-checker

Tell the Rust compiler that a returned value doesn't hold the reference provided in the constructing function


I have some structs containing some data that look like this:

struct A {
    // Some data
}

struct B<'s> {
    a: &'s mut A
    c: C<'s>
}

impl<'s> B<'s> {
    fn new(a: &'s mut A, d: &'s D) -> B<'s> {
        let c = C::new(d);
        B { a, c }
    }

    fn extract_c(self) -> C<'s> {
        self.c
    }
}

// A smart pointer provided by a library
struct C<'s> {
    d: &'s D,
    // Other fields holding some data, not referencing A
}

struct D {
    // Some other data not referencing A in any way (provided by a library)
}

I also have functions that that create and modify said structs like this:

fn modify(b: &mut B) {
    // Modifies b.a and b.c
}

fn construct_c<'s>(a: &'s mut A, d: &'s D) -> C<'s> {
    let mut b = B::new(a, d); // C is created in B::new
    modify(&mut b);
    b.extract_c()
}

I want to use construct_c somewhere else such that I can make another reference to A after obtaining the result from the call, like this:

fn main() {
    let mut a = A::new();
    let d = D::new();
    let c = construct_c(&mut a, &d);  // First mutable borrow of `a`
    println!("{a}");   // Second borrow of `a`
    let result = do_sth_with_c(c);  // Move of `c`
    do_sth_with_a(a);
    // Some other code...
}

However, when I try to do that, the compiler says that when I call do_sth_with_c, I am using the first mutable borrow of a, even though c doesn't hold the reference to a provided to construct_c.

When I removed println!("{a}"); and do_sth_with_a the code compiles, but I really need to print that information and do it before calling do_sth_with_c. Is there a way to tell the compiler that C knows nothing about the reference to a and that it is safe to make new references after calling construct_c?

EDIT 1: When I substituted all the references &mut A with Rc<RefCell<A>> the code compiles. But is there another way without using Rc and RefCell?

EDIT 2: Following this answer, introducing another lifetime to B seems to resolve the issue.

How B changed:

struct B<'a, 's> {
    a: &'a mut A,
    c: C<'s>,
}

// All functions that use `B` need to be updated as well

Solution

  • You wrote in a comment about C, "some other data not referencing A in any way". But by using the same lifetime you're telling the compiler that they are related. So, change your definitions from

    struct B<'s> {
        a: &'s mut A
        c: C<'s>
    }
    
    fn construct_c<'s>(a: &'s mut A, d: &'s D) -> C<'s> {
        let mut b = B::new(a, d);
        modify(&mut b);
        b.extract_c()
    }
    

    to

    struct B<'s, 'c> {
        a: &'s mut A
        c: C<'c>
    }
    
    fn construct_c<'s, 'c>(a: &'s mut A, d: &'c D) -> C<'c> {
        let mut b = B::new(a, d);
        modify(&mut b);
        b.extract_c()
    }
    

    Now each lifetime can vary independently, and in particular, the 'c lifetime is allowed to be longer than 's.

    That said, I have to warn you that you may be trying to use references where owned data would be better. A common beginner mistake is to try to build data storage structures out of references, and this is usually not a good idea because it either turns out impossible, or highly constrains how the structure can be used.