Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot borrow variable when borrower scope ends

Tags:

rust

I can't understand why a mutable borrowed variable is still borrowed after the scope of the borrower ends. It looks like it is related to trait usage, but I don't see why:

fn main() {
    let mut a = 10;
    test::<FooS>(&mut a);
    println!("out {:?}", a)
}

trait Foo<'a> {
    fn new(data: &'a mut u32) -> Self;
    fn apply(&mut self);
}

struct FooS<'a> {
    data: &'a mut u32,
}

impl<'a> Foo<'a> for FooS<'a> {
    fn new(data: &'a mut u32) -> Self {
        FooS { data: data }
    }

    fn apply(&mut self) {
        *self.data += 10;
    }
}

fn test<'a, F>(data: &'a mut u32)
    where F: Foo<'a>
{
    {
        // let mut foo = FooS {data: data}; // This works fine
        let mut foo: F = Foo::new(data);
        foo.apply();
    } // foo scope ends here
    println!("{:?}", data); // error
} // but borrowed till here

try online

error: cannot borrow `data` as immutable because `*data` is also borrowed as mutable [--explain E0502]
   --> <anon>:34:22
31  |>         let mut foo: F = Foo::new(data);
    |>                                   ---- mutable borrow occurs here
...
34  |>     println!("{:?}", data); // error
    |>                      ^^^^ immutable borrow occurs here
35  |> } // but borrowed till here
    |> - mutable borrow ends here
like image 639
Pavel Shander Avatar asked Aug 02 '16 06:08

Pavel Shander


1 Answers

The test function requires that type F implements Foo<'a>. The 'a there is a lifetime parameter that's passed to the function. Lifetime parameters always represent lifetimes that live longer than the function call – because there is just no way a caller could supply a reference with a shorter lifetime; how could you pass a reference to a local variable from another function? –, and for the purposes of borrow checking (which is local to a function), the compiler considers that the borrow covers the whole function call.

Therefore, when you create an instance of F from the call to Foo::new, you create an object that borrows something with lifetime 'a, a lifetime that covers the whole function call.

It's important to understand that when you call test::<FooS>, the compiler actually fills in a lifetime parameter for FooS<'a>, so you end up calling test::<FooS<'a>>, where 'a is the region that covers the statement that contains the function call (because &mut a is a temporary expression). Therefore, the compiler thinks that the FooS that would be constructed in test would borrow something until the end of the statement with the call to test!

Let's contrast this with the nongeneric version:

let mut foo = FooS {data: data};

In this version, the compiler chooses a concrete lifetime for FooS<'a> in test, rather than in main, so it will choose the block suffix extending from the end of the let statement to the end of the block, which means that the next borrow of data doesn't overlap and there's no conflict.

What you really want is that F implement Foo<'x> for some lifetime 'x that is shorter than 'a, and most importantly, that lifetime must be a region within the function, not an enclosing one like 'a is.

Rust's current solution to this problem is higher-ranked trait bounds. It looks like this:

fn test<'a, F>(data: &'a mut u32)
    where F: for<'x> Foo<'x>
{
    {
        let mut foo: F = Foo::new(data);
        foo.apply();
    }
    println!("{:?}", data);
}

In words, it means the type F must implement Foo<'x> for every possible 'x.

While this version of test compiles on its own, we cannot actually supply a type that fulfills this constraint, because for every possible lifetime 'a, there is a distinct type FooS<'a> that only implements Foo<'a>. If FooS had no lifetime parameter and the impl of Foo for FooS looked like this:

impl<'a> Foo<'a> for FooS {

then it would be fine, since there is a single type FooS that implements Foo<'a> for every possible lifetime 'a.

Of course, you can't remove the lifetime parameter on FooS, as it contains a borrowed pointer. The proper solution for this problem is a feature that Rust doesn't have yet: the ability to pass a type constructor (rather than a fully constructed type) as a generic parameter to a function. With this capability, we could call test with FooS, a type constructor that needs a lifetime parameter to produce a concrete type, without specifying the concrete lifetime at the call site, and the caller would be able to supply its own lifetime.

like image 140
Francis Gagné Avatar answered Oct 18 '22 21:10

Francis Gagné