Borrowing issue in loop

I need a help to solve a borrowing problem.

Playground link

The code in the link above is not practical, it's just a abstracted version of a code which I'm writing.
If you compile the code above, it will emit an error like this:

error[E0506]: cannot assign to `*data` because it is borrowed
  --> src/main.rs:15:9
   |
10 | fn foo<'a, F>(data: &'a mut Data, f: F) 
   |        -- lifetime `'a` defined here
...
15 |         *data = Data(i);
   |         ^^^^^^^^^^^^^^^ assignment to borrowed `*data` occurs here
16 |         let condition = f(data);
   |                         -------
   |                         | |
   |                         | borrow of `*data` occurs here
   |                         argument requires that `*data` is borrowed for `'a`

error[E0499]: cannot borrow `*data` as mutable more than once at a time
  --> src/main.rs:20:17
   |
10 | fn foo<'a, F>(data: &'a mut Data, f: F) 
   |        -- lifetime `'a` defined here
...
16 |         let condition = f(data);
   |                         -------
   |                         | |
   |                         | first mutable borrow occurs here
   |                         argument requires that `*data` is borrowed for `'a`
...
20 |             i = data.generate_new();
   |                 ^^^^ second mutable borrow occurs here

Compiler says the data is borrowed because of calling a function with it, but the function f just returns a bool value, not reference of anything. Therefore I think the borrowing of data should be ended when the returned bool value has assigned to a local variable and line 15 or 20 should be fine.

It would be grateful if someone explain what is going on and what I should do to avoid this error.

The problem is the bounds on F require that it borrow data for the full lifetime 'a. The fact that the return value doesn't contain any lifetimes is irrelevant.

You need a higher-ranked trait bound here:

fn foo<'a, F>(data: &'a mut Data, mut f: F)
where
    F: for<'b> FnMut(&'b mut Data) -> bool,

(note I changed Fn to FnMut, which is more general)

2 Likes

Your problem lies in the function signature of foo. Note that when you don’t try to manually specify any lifetimes, it compiles fine.

fn foo<F>(data: &mut Data, f: F) 
where F: Fn(&mut Data) -> bool
{
    let mut i = 1;
    loop {
        *data = Data(i);
        let condition = f(data);
        if condition {
            return;
        } else {
            i = data.generate_new();
        }
    }
}

The problem is that you artificially restrict the signature by adding 'a manually and using it in multiple places, including the argument of f: F. This means that the compiler assumes that f needs to borrow it argument for the whole lifetime of the data reference. Since this borrow is also mutable, it can’t work more than once, but it’s in a loop, hence the error.

To understand how eliding the lifetime helped above, let’s see what the above code is desugared to:

fn foo<'a, F>(data: &'a mut Data, f: F) 
where F: for<'b> Fn(&'b mut Data) -> bool
{
    let mut i = 1;
    loop {
        *data = Data(i);
        let condition = f(data);
        if condition {
            return;
        } else {
            i = data.generate_new();
        }
    }
}

You see a higher ranked lifetime here, introduced by special lifetime elision rules for function pointer types and Fn* traits.

The idea is that this correct signature of foo requires f to be applicable to &'b mut Data references of any lifetime. Each call of f can get a different lifetime here (and it does, as it’s a new re-borrow of data in a different loop iteration).

Edit: I also wanted to point out that you consistently mis-spelled “borrowing”. I’ve already fixed the title for you, too.

3 Likes

Thanks! your replies are really helpful to understand the problem.

I have also fixed my main text as you advised. :smile: thanks again!

FYI, “burrow” is a verb too, but it means something entirely different.

Yes! it is. I'm not fluent in English, so I used it involuntarily. The using of "burrow" might come from Starcraft since I played the game a lot :slight_smile:

2 Likes