Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mutable borrow seems to outlive its scope

Tags:

rust

Trying to compile this program I get stuck on the borrow checker:

use std::collections::BTreeMap;

type Object<'a> = BTreeMap<&'a str, i32>;

struct Root<'a>(Object<'a>);

struct Sub<'a>(&'a mut Object<'a>, &'a str);

impl<'a> Root<'a> {
    fn borrow_mut(&'a mut self, data: &'a str) -> Sub<'a> {
        Sub(&mut self.0, data)
    }

    fn borrow(&self) {
        println!("{:?}", self.0);
    }
}

fn main() {
    let mut me = Root(Object::new());
    {
        me.borrow_mut("data!");
    }
    me.borrow();
}

(Playground)

I get:

error[E0502]: cannot borrow `me` as immutable because it is also borrowed as mutable
  --> src/main.rs:24:5
   |
22 |         me.borrow_mut("data!");
   |         -- mutable borrow occurs here
23 |     }
24 |     me.borrow();
   |     ^^ immutable borrow occurs here
25 | }
   | - mutable borrow ends here

It looks like the mutable borrow should end before me.borrow() but the borrow checker insists that it ends when main ends.

To quickly explain what I'm trying to accomplish:

  1. Make a parent struct to hold data
  2. Make a sub category of data and store it in the parent
  3. Use this builder style pattern to make MongoDB Queries
like image 531
Michael Eden Avatar asked Sep 04 '15 17:09

Michael Eden


1 Answers

You are running afoul of a lifetime issue.

There are multiple different lifetimes in your program:

  • type Object<'a> = BTreeMap<&'a str, i32>; => this is one
  • &'a mut Object<'a> => there are up to TWO here
  • struct Sub<'a>(&'a mut Object<'a>, &'a str); => there are up to THREE here

There is, apparently, no reason for the reference to Object<'a> to have the same lifetime than the &str inside the BTreeMap. However, you told the compiler that you wanted both lifetimes to be the same!

When you write:

struct Sub<'a>(&'a mut Object<'a>, &'a str);

you are telling the compiler that:

  • the lifetime of the &str inside BTreeMap
  • the lifetime of the reference to Object<'_>
  • the lifetime of the &str accompanying the Object<'_>

are all one and the same.

You have over-constrained the requirements; and as a result no solution can satisfy them.

Adding one more degree of freedom is sufficient! We'll just make the lifetime of the reference to Object<'_> different from the lifetime of those &str floating around:

struct Sub<'a, 'b: 'a>(&'a mut Object<'b>, &'b str);

impl<'b> Root<'b> {
    fn borrow_mut<'a>(&'a mut self, data: &'b str) -> Sub<'a, 'b> {
        Sub(&mut self.0, data)
    }

    fn borrow(&self) {
        println!("{:?}", self.0);
    }
}

Note the subtle 'b: 'a:

  • Object<'b> contains a reference to something whose lifetime is 'b
  • the lifetime of the reference to Object<'b> (denoted 'a) must be SHORTER than 'b (otherwise you have a reference to something dead?)

Thus, we say that 'b outlives 'a with 'b: 'a.

And that's it. Simply loosening the requirements allow the compiler to allow your code to compile.


Note that in general, if you find yourself writing something like &'a &'a str you are doing it wrong. If you think about it, you will realize that in order to create a reference to something, it must first be. And therefore a reference to an object necessarily has a shorter lifetime than the object itself (ever so slightly).

like image 175
Matthieu M. Avatar answered Nov 15 '22 10:11

Matthieu M.