Kai Schmidt
Kai Schmidt

Reputation: 731

References passed to functions passed to functions remain borrowed

Consider the following Rust code:

fn foo<'a, T, F, G>(x: &'a mut T, f: F, g: G)
where
    T: 'a,
    F: Fn(&'a T) -> &'a T,
    G: Fn(&'a mut T) -> &'a mut T,
{
    {
        f(x);
    }
    g(x);
}

fn main() {
    let mut x = 5;
    foo(&mut x, |a| a, |a| a);
}

This gives the compiler error:

error[E0502]: cannot borrow `*x` as mutable because it is also borrowed as immutable
  --> src/main.rs:10:7
   |
8  |         f(x);
   |           - immutable borrow occurs here
9  |     }
10 |     g(x);
   |       ^ mutable borrow occurs here
11 | }
   | - immutable borrow ends here

I do not understand why the immutable borrow of x ends on line 11. For one, f(x) is in an inner scope which ends on line 9. However, the return value of f(x) is not bound to any variable, so I would think the borrow should end on line 8, and the inner scope shouldn't even be necessary.

Upvotes: 4

Views: 106

Answers (1)

mcarton
mcarton

Reputation: 30011

Let's consider this example:

fn foo<'a, T, F, G>(x: &'a mut T, mut f: F, g: G)
where
    T: 'a,
    F: FnMut(&'a T) -> &'a T,
    G: Fn(&'a mut T) -> &'a mut T,
{
    {
        f(x);
    }
}

fn main() {
    let mut x = 5;
    let mut y = std::cell::RefCell::new(&0);
    foo(&mut x, |a| { y.replace(&a); a }, |a| a);
}

This is perfectly legal because the function f is guaranteed to take a reference with the same lifetime as x, so it can store a reference to x. But then you can't call g with x because f might have stored x already.

If you change foo to:

fn foo<T, F, G>(x: &mut T, mut f: F, g: G)
where
    F: FnMut(&T) -> &T,
    G: Fn(&T) -> &T,

which is equivalent (due to lifetime elision rules) to:

fn foo<'a, T, F, G>(x: &'a mut T, mut f: F, g: G)
where
    T: 'a,
    F: for<'b> FnMut(&'b T) -> &'b T,
    G: for<'c> Fn(&'c T) -> &'c T,

Then f isn't allowed to store the reference x:

error: borrowed data cannot be stored outside of its closure
  --> src/main.rs:14:33
   |
13 |     let mut y = std::cell::RefCell::new(&0);
   |         -----                           -- cannot infer an appropriate lifetime...
   |         |
   |         ...so that variable is valid at time of its declaration
14 |     foo(&mut x, |a| { y.replace(&a); a }, |a| a);
   |                 ---             ^^ cannot be stored outside of its closure
   |                 |
   |                 borrowed data cannot outlive this closure

but calling foo as foo(&mut x, |a| a, |a| a); becomes legal.

Upvotes: 4

Related Questions