Returning Mutable borrows in a loop

fn main() {
    println!("Hello, world!");
    
    let mut foo = Foo { bar: &"dood" };
    let mut b = Baz { foo: None };
    assign_hold(&mut foo, &mut b);
}

struct Foo<'a> {
    bar: &'a str
}

struct Baz<'a, 'b> {
    foo: Option<&'a mut Foo<'b>>
}

fn produce_mut_ref<'a, 'b>(x: &'a mut Foo<'b>) -> &'a mut Foo<'b> where 'b: 'a{
    return x;
}

fn assign_hold<'a, 'b>(f: &'a mut Foo<'a>, b: &'a mut Baz<'a, 'a>){
    let mut x = 0;
    loop {
        
        b.foo = Some(produce_mut_ref(f));
        b.foo = None;
        
        x+=1;
        if x > 5 {
            
            break;
        }
    }
    
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0499]: cannot borrow `*f` as mutable more than once at a time
  --> src/main.rs:25:38
   |
21 | fn assign_hold<'a, 'b>(f: &'a mut Foo<'a>, b: &'a mut Baz<'a, 'a>){
   |                -- lifetime `'a` defined here
...
25 |         b.foo = Some(produce_mut_ref(f));
   |                      ----------------^-
   |                      |               |
   |                      |               mutable borrow starts here in previous iteration of loop
   |                      argument requires that `*f` is borrowed for `'a`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0499`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

i have a similar situation to that where i think because Baz is holding onto a mutable reference of Foo produced in a loop, it will lock all future borrows. I read a similar thread :

The ways to circumvent this of course are to use smart pointers, make structs own the data they have, etc, but what if I can't use either of these solutions? This works if assign_hold takes raw pointer to a Foo, but before I do that, is there anyway to make this work without using smart pointers or making structs own their data before I go into unsafe?

I haven't looked too closely, because I'm tired, but &'a Foo<'a> is often a mistake, because it equates the lifetime of the container with the lifetime it contains. Usually, you want &'a Foo<'b> where 'b: 'a which is to say, the data contained in the type (lifetime b) outlives the reference being passed around (lifetime a).

1 Like
fn produce_mut_ref<'a, 'b>(x: &'a mut Foo<'b >) ->  &'a mut Foo<'b> where 'b: 'a {
    return x;
}

so this would be how to annotate it correctly? It doesn't seem to fix it but thanks, I learned something new as to what <'a> is actually expressing.

Does the struct annotation also conflate the lifetime it holds with the lifetime of the struct/container?

Now that I have some sleep, and am looking more closely at your problem, I see some other issues.

It looks like you are trying to call something that takes a mutable borrow multiple times in a loop while the borrow is retained across loop iterations. You are overwriting the old borrow, so conceptually, this makes sense, but the new borrow is created before the old borrow is dropped. It might help to reassign b.foo to None at the end of each person, so the borrow is gone before it is retake.

Also, in your example code the loop will break on the first round because 1 < 5, (which may be unintended), so at runtime the double borrow will never happen, but the compiler can't rely on that fact when analyzing the lifetimes.

Thanks! I fixed the loop but I still cant compile even though I set it to None right after. Is there any other way to get the borrow to drop on the next iteration of the loop?

Note there's nothing wrong with &'a Type<'a>; variance lets this work anywhere &'a Type<'b> does. The problem is specifically with &'a mut Type<'a>, because &mut T is invariant in T.


Why does Baz need to hold an Option<&mut Foo>? That sounds like a recipe for pain and misfortune. Lifetimes in structs should be avoided whenever possible.

Yeah, the &mut reference it holds comes from a huge chunk of data from an unsafe VirtualAlloc() call that returns a long pointer to void, so its already on the heap, so I thought using smart pointers would be bad. Is it?

The reason why this code is structured the way it is, is because it's a direct 1-1 translation of a C++ game (Platform-independent Game Memory  —  Handmade Hero  —  Episode Guide  —  Handmade Hero) , which stores *mut T everywhere and freely on structs. Since C++ pointers can be null, I thought the translation to rust of a c++ pointer would be Option<&mut ref>

Yikes. That's bad news. If it ever needs to be stored in multiple places at the same time, it's going to make the compiler very sad. Rather than placing the pointer on a struct, I would suggest passing in the pointer as a separate argument to methods that need it.


Yeah, the &mut reference it holds comes from a huge chunk of data from an unsafe VirtualAlloc() call that returns a long pointer to void, so its already on the heap, so I thought using smart pointers would be bad. Is it?

Smart pointers wouldn't help much here even if you could use them. VirtualAlloc basically returns an Option<&'static mut T>, which is already a type that can live forever. The problem is that, when you have to pass it into a function that wants to mutate it, rust wants to know that the pointer given to that function won't alias anything else. So regardless of whether it was initially a &'static mut T or a Box<T> or some kind of smart pointer or whatever, you need to pass a &'a mut T (where 'a < 'static) at the function boundary so that the function doesn't borrow it forever.

The only way around this is with something like RefCell or RwLock. Then you can get away with &'a RefCell<T>, which is free from the compile-time alias checking of &'a mut T. But those will panic or block when accessed in a manner that would alias, which will probably happen very quickly if you're trying to work with some large, multipurpose thing like a RefCell<game_memory>! To have any hope of making that work, you'll probably need to create RefCells at a very granular level on small pieces of data that you are certain will never need to be borrowed twice mutably.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.