3

I have a function that will either return a reference to existing item from a Vec, or push a new item onto the Vec and return a reference to that existing item. I created a basic exmple that illustrates what I want to do:

struct F {
    x: Vec<Vec<String>>,
}

impl F {
    fn foo(&mut self, s: String) -> &[String] {
        for strings in &self.x {
            if strings.contains(&s) {
                return &strings;
            }
        }

        self.x.push(vec![s]);

        &self.x[self.x.len() - 1]
    }
}

But when I try to compile this, I get an error about lifetimes:

error[E0502]: cannot borrow `self.x` as mutable because it is also borrowed as immutable
  --> src/lib.rs:13:9
   |
6  |     fn foo(&mut self, s: String) -> &[String] {
   |            - let's call the lifetime of this reference `'1`
7  |         for strings in &self.x {
   |                        ------- immutable borrow occurs here
8  |             if strings.contains(&s) {
9  |                 return &strings;
   |                        -------- returning this value requires that `self.x` is borrowed for `'1`
...
13 |         self.x.push(vec![s]);
   |         ^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

I don't understand this error, because in my mind the immutable borrow on line 7 is guaranteed to no longer exist by line 13, since the function will either have returned before line 13, or the for loop will have ended, and the borrow should end with it. What am I missing?

hellow
  • 12,430
  • 7
  • 56
  • 79
Lily Mara
  • 3,859
  • 4
  • 29
  • 48
  • I think this is an excellent example of some code that *should* work but doesn't; maybe file an issue with the Rust compiler? I would have thought NLL would solve this. – Sebastian Redl Apr 26 '19 at 06:41

2 Answers2

2

I think this is a limitation of current borrow checker, you can do this instead:

struct F {
    x: Vec<Vec<String>>,
}

impl F {
    fn foo(&mut self, s: String) -> &[String] {
        let ret = self.x.iter().position(|strings| strings.contains(&s));

        if let Some(ret) = ret {
            &self.x[ret]
        } else {
            self.x.push(vec![s]);
            &self.x.last().unwrap()
        }
    }
}
Stargateur
  • 24,473
  • 8
  • 65
  • 91
0

Stargateur is right and the borrow checker cannot prove that your code is correct. We have to help it.

Another possibility is to use an index while iterating over the Vecs.

struct F {
    x: Vec<Vec<String>>,
}

impl F {
    fn foo(&mut self, s: String) -> &[String] {
        for (i, strings) in self.x.iter().enumerate() {
            if strings.contains(&s) {
                return &self.x[i];
            }
        }

        self.x.push(vec![s]);
        self.x.last().unwrap()
    }
}

(Also use slice::last instead of manually getting the index. It's more clear what you want to do).

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
hellow
  • 12,430
  • 7
  • 56
  • 79