Borrow Checking
Rust’s borrow checker puts constraints on the ways you can borrow values. We’ve already seen that a reference cannot outlive the value it borrows:
There’s also a second main rule that the borrow checker enforces: The aliasing rule. For a given value, at any time:
- You can have one or more shared references to the value, or
- You can have exactly one exclusive reference to the value.
Speaker Notes
This slide should take about 10 minutes.
- The “outlives” rule was demonstrated previously when we first looked at references. We review it here to show students that the borrow checking is following a few different rules to validate borrowing.
- The above code does not compile because
a
is borrowed as mutable (throughc
) and as immutable (throughb
) at the same time.- Note that the requirement is that conflicting references not exist at the
same point. It does not matter where the reference is dereferenced. Try
commenting out
*c = 20
and show that the compiler error still occurs even if we never usec
. - Note that the intermediate reference
c
isn’t necessary to trigger a borrow conflict. Replacec
with a direct mutation ofa
and demonstrate that this produces a similar error. This is because direct mutation of a value effectively creates a temporary mutable reference.
- Note that the requirement is that conflicting references not exist at the
same point. It does not matter where the reference is dereferenced. Try
commenting out
- Move the
println!
statement forb
before the scope that introducesc
to make the code compile.- After that change, the compiler realizes that
b
is only ever used before the new mutable borrow ofa
throughc
. This is a feature of the borrow checker called “non-lexical lifetimes”.
- After that change, the compiler realizes that
More to Explore
- Technically multiple mutable references to a piece of data can exist at the same time via re-borrowing. This is what allows you to pass a mutable reference into a function without invaliding the original reference. This playground example demonstrates that behavior.
- Rust uses the exclusive reference constraint to ensure that data races do not occur in multi-threaded code, since only one thread can have mutable access to a piece of data at a time.
- Rust also uses this constraint to optimize code. For example, a value behind a shared reference can be safely cached in a register for the lifetime of that reference.
- Fields of a struct can be borrowed independently of each other, but calling a method on a struct will borrow the whole struct, potentially invalidating references to individual fields. See this playground snippet for an example of this.