borrow-checker-faqs
I got lots of positive feedback about the FAQ section in my Rust 2020 blog post, so I'm trying that format again for another topic that's been requested a lot: How to fix common borrow-checker issues. This isn't meant to explain how or why the borrow checker works the way it does (see The Nomicon or Two Beautiful Rust Programs for that), just how to work around some of its current limitations.
In a small break from the format, the 'questions' will instead be Rust code and the accompanying compiler error. I make no pretense that the code is doing useful work, it's just meant to give examples of errors.
FAQ
How do I modify elements in a collection while also modifying the collection?
let mut queue = vec!;
for elem in queue.iter_mut
error[E0499]: cannot borrow `queue` as mutable more than once at a time
--> src/main.rs:5:9
|
3 | for elem in queue.iter_mut() {
| ----------------
| |
| first mutable borrow occurs here
| first borrow later used here
4 | *elem += 2;
5 | queue.push(*elem + 1);
| ^^^^^ second mutable borrow occurs here
This happens because for-loops borrow the iterator for the whole loop, not just for a single
iteration. You can work around it by using while let
instead:
let mut queue = vec!;
while let Some = queue.pop
Note that this has a behavior change: unlike the previous loop, this removes the element from the
queue before modifying it. You can get the behavior from before back by adding queue.insert(0, elem)
(although at that point you may want to use a
VecDeque
).
I have disjoint fields in a struct, but because I use one in a closure, I can't use the other.
let mut s = S ;
spawn;
println!;
error[E0382]: borrow of moved value: `s`
--> src/main.rs:18:16
|
8 | let s = S {
| - move occurs because `s` has type `S`, which does not implement the `Copy` trait
...
12 | std::thread::spawn(|| {
| -- value moved into closure here
13 | for x in s.elems {
| ------- variable moved due to use in closure
...
18 | println!("{}", s.metadata);
| ^^^^^^^^^^ value borrowed here after move
This happens because the closure captures the whole s
struct, not just the fields it needs.
You can explicitly say which fields to capture by adding a let
statement:
let elems = s.elems;
spawn;
This is done automatically for you with #![feature(capture_disjoint_fields)]
, which will
hopefully be enabled by default in the 2021 edition.
Similar issues happen for structs that aren't Send
or Sync
:
use RefCell;
let s = S ;
spawn;
println!;
error[E0277]: `RefCell<i32>` cannot be shared between threads safely
--> src/main.rs:16:1
|
16 | std::thread::spawn(|| {
| ^^^^^^^^^^^^^^^^^^ `RefCell<i32>` cannot be shared between threads safely
|
= help: within `S`, the trait `Sync` is not implemented for `RefCell<i32>`
= note: required because it appears within the type `S`
= note: required because of the requirements on the impl of `Send` for `&S`
= note: required because it appears within the type `[closure@src/main.rs:16:20: 20:2]`
error: aborting due to previous error
If you only need a few of the fields, you can add explicit let
statements in the same way.
let elems = s.elems;
spawn;
println!;
Moving a line of code into a separate function makes it fail to compile.
There are actually quite a few things that will cause this. The most common are:
- The compiler can no longer tell you're using disjoint fields.
- You wrote the wrong lifetimes (or the elided lifetimes are wrong).
An example of 1 (Playground):
let mut s = S ;
let _elems = s.elems; // moves out of `elems`
s.metadata = 1; // works fine
s.set_metadata; // breaks because `elems` is moved
error[E0382]: borrow of partially moved value: `s`
--> src/main.rs:22:1
|
20 | let elems = s.elems; // moves out of `elems`
| ------- value partially moved here
21 | s.metadata = 1; // works fine
22 | s.set_metadata(1); // breaks because `elems` is moved
| ^ value borrowed here after partial move
|
= note: partial move occurs because `s.elems` has type `Vec<i32>`, which does not implement the `Copy` trait
There is no simple fix for this; this is the main reason getters and setters are discouraged in Rust. You can either manually inline the code, or change your struct so that the relevant parts use composition instead of all being in the same struct:
;
let mut s = S ;
let _elems = s.elems; // moves out of `elems`
s.metadata = Inner; // works fine
s.metadata.set_metadata; // works fine
An example of 2 (Playground):
;
let x = 0;
let _p = ;
let _q = ;
This one is tricky to spot: the issue isn't the code you wrote, but rather the code you didn't write.
If you desugar inner()
, it would be something like
But this is unnecessarily restrictive - self.0
lives longer than self
. The fix is to write the lifetime yourself:
There are a lot more variants of this. I might publish a follow-up post with more.
How can I use an iterator from a struct when I also need mutable access? I know that my changes won't affect the iterator.
// same `s` from earlier example
let mut s = S ;
for x in s.elems.iter
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:13:17
|
12 | for x in s.elems.iter() {
| --------------
| |
| immutable borrow occurs here
| immutable borrow later used here
13 | takes_s(*x, &mut s);
| ^^^^^^ mutable borrow occurs here
You can .collect
the iterator before using it:
let iter: = s.elems.iter.copied.collect;
for x in iter
Note that this isn't ideal in several ways:
- If you're wrong that
takes_s
doesn't affect the iterator, it changes the behavior. - It makes the iterator eager instead of lazy, which means you have to allocate a new collection
(you don't strictly have to use
Vec
, but there's no reason to use anything else). - It requires either copying or cloning the elements.
However, in some cases, there's no alternative. See this Rustdoc PR for a real-world example.