Question about borrowing

Hi,
I've hit this problem a few times since developping in Rust, and I was wondering if I'm doing something wrong or if there's something I'm not understanding properly.

This is not actual code from a project, but simply represents a pattern I have to write from time to time:

struct Test {
	v: Vec<(String, u32)>,
}

impl Test {

	fn find_or_add(&mut self, key: String, value: u32) -> &u32 {
		let r = self.v.iter().find(|(k, _)| **k==key);
		if let Some ((_, v)) = r {
			return v;
		}

		drop(r);

		self.v.push((key, value));
		&self.v.last().unwrap().1
	}

}

The borrow checker complains about "self.v.push".
It says "self.v" cannot be borrowed as mutable, because it's already borrowed immutably by iter above.
Since the loop either returns or completes, the code under it should be able to borrow self.v again!
I even added the drop(r) to drop any reference that might linger.
The way I understand the borrow checker, past the drop(r) call, it should be like a clean slate, nothing
borrowed.

What am I missing ?

(As you can see from the test example, it's a get_or_add() pattern, but I just wrote this quickly to demonstrate the issue I'm having. Actual code differs slightly, but the principle is the same)

Could you introduce a new scope?

struct Test {
	v: Vec<(String, u32)>,
}

impl Test {

	fn find_or_add(&mut self, key: String, value: u32) -> &u32 {
       {
	        let r = self.v.iter().find(|(k, _)| **k==key);
	        if let Some ((_, v)) = r {
			    return v;
		    }
       }
		//drop(r); can probably remove this?

		self.v.push((key, value));
		&self.v.last().unwrap().1
	}

}

This will make sure rust knows that the borrow has ended

I tried to put it in a scope like you wrote, I get the exact same error message as before.

drop(r) doesn't do anything (r is None at this point anyway), I just added it for emphasis.
Adding/removing it doesn't change anything.

That drop() cannot possibly matter, because references are Copy. If anything, borrowck could figure out that the borrow ended from the control flow (return).

IIRC this code will be accepted by the next-generation borrow checker, Polonius.

Yeah like I said, that drop doesn't have any meaning, I just put it there for emphasis.

I still don't understand why it won't compile though. Usually the borrow checker is pretty good
at detecting when variables are used, but it this case I don't get it.

If I write this, which is functionally the same, but doesn't look as good.. It works fine:

	fn find_or_add_loop(&mut self, key: String, value: u32) -> &u32 {
		for i in 0..self.v.len() {
			if self.v[i].0==key {
				return &self.v[i].1;
			}
		}

		self.v.push((key, value));
		&self.v.last().unwrap().1
	}

Yeah, it's NLL Problem Case #3. Here's the issue and a post with some details. Technically the current borrow checker can do it, but the analysis takes too long. The next generation borrow checker is available on nightly (but is also incomplete).

3 Likes

Oh ok so it's a borrow checker limitation then..

At least I'm glad I didn't misunderstand something!

Thanks,
Jonathan

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.