Avoiding closure captures that cause borrow conflicts

I'm just getting into Rust, so another beginner question... I've been doing stuff like the code below in C++ a lot. I'll use a closure as an inner helper function that captures local variables. But in Rust the rules don't let me do this. How would you get around this error?

It seems to me that finish() doesn't actually borrow my_str until finish runs. But it seems Rust considers it borrowed when the closure is constructed, so the mutable borrow with push_str can't happen.

fn closure_capture_example() {
    let mut my_str = String::new();
    let finish = || -> i32 {
        let n = my_str.parse().unwrap();
        println!("finishing, parsing i32: {n}");
        n
    };
    my_str.push_str("123");
    let i = finish();
    println!("done, i = {i}")
}

The error is:

error[E0502]: cannot borrow `my_str` as mutable because it is also borrowed as immutable

156 |     let finish = || -> i32 {
    |                  --------- immutable borrow occurs here
157 |         let n = my_str.parse().unwrap();
    |                 ------ first borrow occurs due to use of `my_str` in closure
...
161 |     my_str.push_str("123");
    |     ^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
162 |     let i = finish();
    |             ------ immutable borrow later used here

Yes, that's how it works in Rust. You can think of closures as a compiler-provided struct where the fields are captures, and it gets constructed on the spot. They don't reborrow captures every call and release them between, or such.

+    my_str.push_str("123");
     let finish = || -> i32 {
         let n = my_str.parse().unwrap();
         println!("finishing, parsing i32: {n}");
         n
     };
-    my_str.push_str("123");

:slight_smile:

There's no silver bullet and the example is too simple to make very meaningful suggestions against (I just wouldn't have a closure for the OP). Generally you just have to treat capturing closures like other types of borrows. Taking arguments instead of capturing may be an option.

3 Likes

I guess my example a bit too simple there, since in the real code I can't move the push_str call like that, but I can follow your suggestion to pass the reference to the helper instead of capturing it.

One option would be to put my_str in a RefCell. That makes "when finish runs" the thing actually checked.

2 Likes

Thanks. I haven't learned about RefCell yet, but I'm on that chapter in the Rust book, so I'll be learning about it soon.