4

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Kai Schmidt
  • 701
  • 8
  • 14
  • This is basically a duplicate of [Rust Borrow checker only complains about borrowing as mutable multiple times when a function that returns a reference with the same lifetime assigned](https://stackoverflow.com/questions/38006421/rust-borrow-checker-only-complains-about-borrowing-as-mutable-multiple-times-whe) – Shepmaster Aug 15 '18 at 20:28
  • @Shepmaster Note quite? That question is about returning with the same lifetime. The question here [fails just as well](https://play.rust-lang.org/?gist=719d29227e850e96d4af0b141452160d&version=stable&mode=debug&edition=2015) when the functions don't return anything. – mcarton Aug 15 '18 at 20:33
  • @mcarton I'm still looking for the true duplicate which uses methods instead of functions. The closures and methods both have the same root problem which is that they can store the reference inside of themselves, as you point out. – Shepmaster Aug 15 '18 at 20:37

1 Answers1

4

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
mcarton
  • 27,633
  • 5
  • 85
  • 95
  • @Shepmaster I'm seriously wondering if don't just have a rustfmt bot editing every single question and answer on the Rust tag. When do you even sleep? :D – mcarton Aug 15 '18 at 20:23
  • I mean... I basically created my implementation of the playground for the sole purpose of being able to run rustfmt. In this Q&A it was particularly egregious, with the generic declarations looking awfully close to Perl-level line noise. – Shepmaster Aug 15 '18 at 20:26
  • *due to lifetime elision rules* — is it actually lifetime elision? I didn't think that HRTBs were part of *elision*. I guess they kind of have to be? – Shepmaster Aug 15 '18 at 20:38
  • 1
    Well, what allows you elide lifetimes but lifetime elision rules? – mcarton Aug 15 '18 at 20:40