This question already has an answer here:
Cannot borrow immutable borrowed content as mutable
(1 answer)
Closed 5 years ago.
I am recreating the arithmetic operations in binary by representing numbers with a vector of booleans. Since the size of each vector can vary, I made a function to match the length of each vector:
fn match_lengths(mut bit_vec0: Vec<bool>, mut bit_vec1: Vec<bool>) -> (Vec<bool>, Vec<bool>) {
{
let (mut shorter, longer) = if bit_vec0.len() < bit_vec1.len() {
(&bit_vec0, &bit_vec1)
} else {
(&bit_vec1, &bit_vec0)
};
let bit_sign = match shorter.last() {
Some(content) => *content,
None => false,
};
for _ in shorter.len()..longer.len() {
shorter.push(bit_sign);
}
}
(bit_vec0, bit_vec1)
}
I get the error
error[E0596]: cannot borrow immutable borrowed content `*shorter` as mutable
--> src/main.rs:15:13
|
15 | shorter.push(bit_sign); // Error here
| ^^^^^^^ cannot borrow as mutable
Even though I declared it with the mut specifier.
The type of shorter is a reference, more precisely a &Vec<bool>, which means it refers to a Vec<bool> which it is not allowed to mutate1.
Declaring the variable as mut shorter only makes the shorter variable mutable, allowing you to e.g. use shorter = ... assignment to make it refer to a different Vec<bool>. Regardless of the variable's mutability, a shared reference of type &Vec<bool> is not allowed to mutate the object it refers to.
What you need to do is make a mutable reference of type &mut Vec<bool>, using &mut bit_vec0 and &mut bit_vec1. This change makes the code compile, and at that point shorter no longer needs to be mut.
Finally, and this is unrelated to the question, match_lengths accepts bit_vec0 and bit_vec1 by value, modifies them, and returns them. Although that certainly works, it is more idiomatic to accept mutable references. Such an approach is more ergonomic for the caller, and more clearly signals the fact that the function doesn't really "return" anything, it really modifies existing objects.
With those modifications, the function looks like this:
fn match_lengths(bit_vec0: &mut Vec<bool>, bit_vec1: &mut Vec<bool>) {
let (shorter, longer) = if bit_vec0.len() < bit_vec1.len() {
(bit_vec0, bit_vec1)
} else {
(bit_vec1, bit_vec0)
};
let bit_sign = match shorter.last() {
Some(content) => *content,
None => false,
};
for _ in shorter.len()..longer.len() {
shorter.push(bit_sign);
}
}
1
This sounds much like C++'s const, but the guarantee in Rust is even stronger than that of const: not only is a shared (non-mut) reference not allowed to mutate the object it refers to, but neither is anyone else! Rust's compiler and runtime prevent creation of a mut reference to an object while any shared references to that object exist.
Related
I've encountered a confusing error about the use of a mutable and immutable borrow at the same time, after I expect the mutable borrow to end. I've done a lot of research on similar questions (1, 2, 3, 4, 5) which has led me to believe my problem has something to do with lexical lifetimes (though turning on the NLL feature and compiling on nightly doesn't change the result), I just have no idea what; my situation doesn't seem to fit into any of the scenarios of the other questions.
pub enum Chain<'a> {
Root {
value: String,
},
Child {
parent: &'a mut Chain<'a>,
},
}
impl Chain<'_> {
pub fn get(&self) -> &String {
match self {
Chain::Root { ref value } => value,
Chain::Child { ref parent } => parent.get(),
}
}
pub fn get_mut(&mut self) -> &mut String {
match self {
Chain::Root { ref mut value } => value,
Chain::Child { ref mut parent } => parent.get_mut(),
}
}
}
#[test]
fn test() {
let mut root = Chain::Root { value: "foo".to_string() };
{
let mut child = Chain::Child { parent: &mut root };
*child.get_mut() = "bar".to_string();
} // I expect child's borrow to go out of scope here
assert_eq!("bar".to_string(), *root.get());
}
playground
The error is:
error[E0502]: cannot borrow `root` as immutable because it is also borrowed as mutable
--> example.rs:36:36
|
31 | let mut child = Chain::Child { parent: &mut root };
| --------- mutable borrow occurs here
...
36 | assert_eq!("bar".to_string(), *root.get());
| ^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here
I understand why an immutable borrow happens there, but I do not understand how a mutable borrow is used there. How can both be used at the same place? I'm hoping someone can explain what is happening and how I can avoid it.
In short, &'a mut Chain<'a> is extremely limiting and pervasive.
For an immutable reference &T<'a>, the compiler is allowed to shorten the lifetime of 'a when necessary to match other lifetimes or as part of NLL (this is not always the case, it depends on what T is). However, it cannot do so for mutable references &mut T<'a>, otherwise you could assign it a value with a shorter lifetime.
So when the compiler tries to reconcile the lifetimes when the reference and the parameter are linked &'a mut T<'a>, the lifetime of the reference is conceptually expanded to match the lifetime of the parameter. Which essentially means you've created a mutable borrow that will never be released.
Applying that knowledge to your question: creating a reference-based hierarchy is really only possible if the nested values are covariant over their lifetimes. Which excludes:
mutable references
trait objects
structs with interior mutability
Refer to these variations on the playground to see how these don't quite work as expected.
See also:
Why does linking lifetimes matter only with mutable references?
How do I implement the Chain of Responsibility pattern using a chain of trait objects?
What are the differences when getting an immutable reference from a mutable reference with self-linked lifetimes?
For fun, I'll include a case where the Rust standard library does this sort of thing on purpose. The signature of std::thread::scope looks like:
pub fn scope<'env, F, T>(f: F) -> T
where
F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T
The Scope that is provided to the user-defined function intentionally has its lifetimes tied in a knot to ensure it is only used in intended ways. This is not always the case since structs may be covariant or contravariant over their generic types, but Scope is defined to be invariant. Also, the only function that can be called on it is .spawn() which intentionally takes &'scope self as the self-parameter as well, ensuring that the reference does not have a shorter lifetime than what is given by scope.
Internally, the standard library contains this documentation (source):
Invariance over 'scope, to make sure 'scope cannot shrink, which is necessary for soundness.
Without invariance, this would compile fine but be unsound:
std::thread::scope(|s| {
s.spawn(|| {
let a = String::from("abcd");
s.spawn(|| println!("{a:?}")); // might run after `a` is dropped
});
});
Even if the lifetime of the reference is invariant with respect to itself, this still avoids many problems above because it uses an immutable reference and interior-mutability. If the parameter to .spawn() required &'scope mut self, then this would not work and run into the same problems above when trying to spawn more than one thread.
The issue isn't lexical lifetimes, and adding an explicit drop won't change the error. The issue is with the &'a mut Chain<'a>- that forces root to be borrowed for its entire life, rendering it useless after the borrow is dropped. As per the comment below, doing this with lifetimes is basically impossible. I would suggest using a box instead. Changing the struct to
pub enum Chain{
Root {
value: String,
},
Child {
parent: Box<Chain>,
},
}
and adjusting the other methods as necesary. Alternatively, using an Rc<RefCell<Chain>> if you want the original to remain usable without consuming self.
Okay, I have Combatants which battle on a Battlefield. My intuition for which things go on which place is a little off. It's pretty close to being a game, but I am now stuck.
I want the Battlefield to have a tick() function, which allows all Combatants to take a decision, like attacking another of the opposite team or moving closing to one if no one is in range. I'm having issues making the borrow checker happy in doing this.
Here's a minimal version which has all the problems of my code.
struct Combatant{
current_health: i16,
max_health: i16
}
struct Battlefield{
combatants: Vec<Combatant>
}
impl Combatant {
fn attack(&self, other: &mut Combatant) {
other.current_health -= 3;
}
}
impl Battlefield {
fn tick(&mut self) {
let target = &mut self.combatants[0];
for combatant in &self.combatants {
combatant.attack(target);
}
}
}
cargo check returns
error[E0502]: cannot borrow `self.combatants` as immutable because it is also borrowed as mutable
--> src/main.rs:20:26
|
19 | let target = &mut self.combatants[0];
| --------------- mutable borrow occurs here
20 | for combatant in &self.combatants {
| ^^^^^^^^^^^^^^^^ immutable borrow occurs here
21 | combatant.attack(target);
| ------ mutable borrow later used here
How can I design this function (or more like, this whole scenario, heh) to make it work in Rust?
Since in your scenario you need to simultaneously have a mutable reference and an immutable reference on two elements in the same container, I think you need the help of interior mutability.
This will check at run-time that the same element is not accessed simultaneously through a mutable (.borrow_mut()) and immutable (.borrow()) reference (otherwise it panics).
Obviously, you have to ensure that by yourself (which is quite ugly since we have to compare pointers!).
Apparently, it is necessary to reach pointers because references cannot be compared (the self argument of std::cmp::PartialEq::eq() will be dereferenced). The documentation of std::ptr::eq() (which should probably be used here) shows the difference between comparing references and comparing pointers.
struct Combatant {
current_health: i16,
max_health: i16,
}
struct Battlefield {
combatants: Vec<std::cell::RefCell<Combatant>>,
}
impl Combatant {
fn attack(
&self,
other: &mut Combatant,
) {
other.current_health -= 3;
}
}
impl Battlefield {
fn tick(&mut self) {
let target_cell = &self.combatants[0];
let target = &*target_cell.borrow();
for combatant_cell in &self.combatants {
let combatant = &*combatant_cell.borrow();
// if combatant as *const _ != target as *const _ {
if !std::ptr::eq(combatant, target) {
let target_mut = &mut *target_cell.borrow_mut();
combatant.attack(target_mut);
}
}
}
}
Note that this interior mutability was bothering me at first, and seemed like « bending the rules » because I was reasoning essentially in terms of « immutable becoming suddenly mutable » (like const-casting in C++), and the shared/exclusive aspect was only the consequence of that.
But the answer to the linked question explains that we should think at first in terms of shared/exclusive access to our data, and then the immutable/mutable aspect is just the consequence.
Back to your example, the shared access to the Combatants in the vector seems essential because, at any time, any of them could access any other.
Because the consequence of this choice (shared aspect) is that mutable accesses become almost impossible, we need the help of interior mutability.
This is not « bending the rules » because strict checking is done on .borrow()/.borrow_mut() (small overhead at this moment) and the obtained Ref/RefMut have lifetimes allowing usual (static) borrow-checking in the portion of code where they appear.
It is much safer than free immutable/mutable accesses we could perform with other programming languages.
For example, even in C++ where we could consider target as const (reference/pointer to const) while iterating on the non-const combatants vector, one iteration can accidentally mutate the target that we consider as const (reference/pointer to const means « I won't mutate it », not « it cannot be mutated by anyone »), which could be misleading. And with other languages where const/mut do not even exist, anything can be mutated at any time (except for objects which are strictly immutable, like str in Python, but it becomes difficult to manage objects with states that could change over time, like current_health in your example).
The problem is this: When you iterate over the combatants, that requires an immutable borrow of all the combatants in that Vec. However, one of them, combatants[0] is already borrowed, and it's a mutable borrow.
You cannot, at the same time, have a mutable and an immutable borrow to the same thing.
This prevents a lot of logic errors. For example, in your code, if the borrow was actually allowed, you'd actually have combatants[0] attack itself!
So what to do? In the specific example above, one thing you could do is use the split_first_mut method of vec, https://doc.rust-lang.org/std/vec/struct.Vec.html#method.split_first_mut
let (first, rest) = self.combatants.split_first_mut();
if let Some(first) = first {
if let Some(rest) = rest {
for combatant in rest {
combatant.attack(first);
}
}
}
You can also use split_at_mut to only iterate on the other elements:
fn tick(&mut self) {
let idx = 0;
let (before, after) = self.combatants.split_at_mut (idx);
let (target, after) = after.split_at_mut (1);
let target = &mut target[0];
for combatant in before {
combatant.attack(target);
}
for combatant in after {
combatant.attack(target);
}
}
Playground
Note that this will panic if idx >= len (self.combatants).
I am trying to write a simple ECS:
struct Ecs {
component_sets: HashMap<TypeId, Box<dyn Any>>,
}
impl Ecs {
pub fn read_all<Component>(&self) -> &SparseSet<Component> {
self.component_sets
.get(&TypeId::of::<Component>())
.unwrap()
.downcast_ref::<SparseSet<Component>>()
.unwrap()
}
pub fn write_all<Component>(&mut self) -> &mut SparseSet<Component> {
self.component_sets
.get_mut(&TypeId::of::<Component>())
.unwrap()
.downcast_mut::<SparseSet<Component>>()
.unwrap()
}
}
I am trying to get mutable access to a certain component while another is immutable. This testing code triggers the error:
fn testing() {
let all_pos = { ecs.write_all::<Pos>() };
let all_vel = { ecs.read_all::<Vel>() };
for (p, v) in all_pos.iter_mut().zip(all_vel.iter()) {
p.x += v.x;
p.y += v.y;
}
}
And the error
error[E0502]: cannot borrow `ecs` as immutable because it is also borrowed as mutable
--> src\ecs.rs:191:25
|
190 | let all_pos = { ecs.write_all::<Pos>() };
| --- mutable borrow occurs here
191 | let all_vel = { ecs.read_all::<Vel>() };
| ^^^ immutable borrow occurs here
My understanding of the borrow checker rules tells me that it's totally fine to get references to different component sets mutably or immutably (that is, &mut SparseSet<Pos> and &SparseSet<Vel>) since they are two different types. In order to get these references though, I need to go through the main ECS struct which owns the sets, which is where the compiler complains (i.e. first I use &mut Ecs when I call ecs.write_all and then &Ecs on ecs.read_all).
My first instinct was to enclose the statements in a scope, thinking it could just drop the &mut Ecs after I get the reference to the inner component set so as not to have both mutable and immutable Ecs references alive at the same time. This is probably very stupid, yet I don't fully understand how, so I wouldn't mind some more explaining there.
I suspect one additional level of indirection is needed (similar to RefCell's borrow and borrow_mut) but I am not sure what exactly I should wrap and how I should go about it.
Update
Solution 1: make the method signature of write_all take a &self despite returning a RefMut<'_, SparseSet<Component>> by wrapping the SparseSet in a RefCell (as illustrated in the answer below by Kevin Reid).
Solution 2: similar as above (method signature takes &self) but uses this piece of unsafe code:
fn write_all<Component>(&self) -> &mut SparseSet<Component> {
let set = self.component_sets
.get(&TypeId::of::<Component>())
.unwrap()
.downcast_ref::<SparseSet<Component>>()
.unwrap();
unsafe {
let set_ptr = set as *const SparseSet<Component>;
let set_ptr = set_ptr as *mut SparseSet<Component>;
&mut *set_ptr
}
}
What are benefits of using solution 1, is the implied runtime borrow-checking provided by RefCell an hindrance in this case or would it actually prove useful?
Would the use of unsafe be tolerable in this case? Are there benefits? (e.g. performance)
it's totally fine to get references to different component sets mutably or immutably
This is true: we can safely have multiple mutable, or mutable and immutable references, as long as no mutable reference points to the same data as any other reference.
However, not every means of obtaining those references will be accepted by the compiler's borrow checker. This doesn't mean they're unsound; just that we haven't convinced the compiler that they're safe. In particular, the only way the compiler understands to have simultaneous references is a struct's fields, because the compiler can know those are disjoint using a purely local analysis (looking only at the code of a single function):
struct Ecs {
pub pos: SparseSet<Pos>,
pub vel: SparseSet<Vel>,
}
for (p, v) in ecs.pos.iter_mut().zip(ecs.vel.iter()) {
p.x += v.x;
p.y += v.y;
}
This would compile, because the compiler can see that the references refer to different subsets of memory. It will not compile if you replace ecs.pos with a method ecs.pos() — let alone a HashMap. As soon as you get a function involved, information about field borrowing is hidden. Your function
pub fn write_all<Component>(&mut self) -> &mut SparseSet<Component>
has the elided lifetimes (lifetimes the compiler picks for you because every & must have a lifetime)
pub fn write_all<'a, Component>(&'a mut self) -> &'a mut SparseSet<Component>
which are the only information the compiler will use about what is borrowed. Hence, the 'a mutable reference to the SparseSet is borrowing all of the Ecs (as &'a mut self) and you can't have any other access to it.
The ways to arrange to be able to have multiple mutable references in a mostly-statically-checked way are discussed in the documentation page on Borrow Splitting. However, all of those are based on having some statically known property, which you don't. There's no way to express “this is okay as long as the Component type is not equal to another call's”. Therefore, to do this you do need RefCell, our general-purpose helper for runtime borrow checking.
Given what you've got already, the simplest thing to do is to replace SparseSet<Component> with RefCell<SparseSet<Component>>:
// no mut; changed return type
pub fn write_all<Component>(&self) -> RefMut<'_, SparseSet<Component>> {
self.component_sets
.get(&TypeId::of::<Component>())
.unwrap()
.downcast::<RefCell<SparseSet<Component>>>() // changed type
.unwrap()
.borrow_mut() // added this line
}
Note the changed return type, because borrowing a RefCell must return an explicit handle in order to track the duration of the borrow. However, a Ref or RefMut acts mostly like an & or &mut thanks to deref coercion. (Your code that inserts items in the map, which you didn't show in the question, will also need a RefCell::new.)
Another option is to put the interior mutability — likely via RefCell, but not necessarily — inside the SparseSet type, or create a wrapper type that does that. This might or might not help the code be cleaner.
I've encountered a confusing error about the use of a mutable and immutable borrow at the same time, after I expect the mutable borrow to end. I've done a lot of research on similar questions (1, 2, 3, 4, 5) which has led me to believe my problem has something to do with lexical lifetimes (though turning on the NLL feature and compiling on nightly doesn't change the result), I just have no idea what; my situation doesn't seem to fit into any of the scenarios of the other questions.
pub enum Chain<'a> {
Root {
value: String,
},
Child {
parent: &'a mut Chain<'a>,
},
}
impl Chain<'_> {
pub fn get(&self) -> &String {
match self {
Chain::Root { ref value } => value,
Chain::Child { ref parent } => parent.get(),
}
}
pub fn get_mut(&mut self) -> &mut String {
match self {
Chain::Root { ref mut value } => value,
Chain::Child { ref mut parent } => parent.get_mut(),
}
}
}
#[test]
fn test() {
let mut root = Chain::Root { value: "foo".to_string() };
{
let mut child = Chain::Child { parent: &mut root };
*child.get_mut() = "bar".to_string();
} // I expect child's borrow to go out of scope here
assert_eq!("bar".to_string(), *root.get());
}
playground
The error is:
error[E0502]: cannot borrow `root` as immutable because it is also borrowed as mutable
--> example.rs:36:36
|
31 | let mut child = Chain::Child { parent: &mut root };
| --------- mutable borrow occurs here
...
36 | assert_eq!("bar".to_string(), *root.get());
| ^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here
I understand why an immutable borrow happens there, but I do not understand how a mutable borrow is used there. How can both be used at the same place? I'm hoping someone can explain what is happening and how I can avoid it.
In short, &'a mut Chain<'a> is extremely limiting and pervasive.
For an immutable reference &T<'a>, the compiler is allowed to shorten the lifetime of 'a when necessary to match other lifetimes or as part of NLL (this is not always the case, it depends on what T is). However, it cannot do so for mutable references &mut T<'a>, otherwise you could assign it a value with a shorter lifetime.
So when the compiler tries to reconcile the lifetimes when the reference and the parameter are linked &'a mut T<'a>, the lifetime of the reference is conceptually expanded to match the lifetime of the parameter. Which essentially means you've created a mutable borrow that will never be released.
Applying that knowledge to your question: creating a reference-based hierarchy is really only possible if the nested values are covariant over their lifetimes. Which excludes:
mutable references
trait objects
structs with interior mutability
Refer to these variations on the playground to see how these don't quite work as expected.
See also:
Why does linking lifetimes matter only with mutable references?
How do I implement the Chain of Responsibility pattern using a chain of trait objects?
What are the differences when getting an immutable reference from a mutable reference with self-linked lifetimes?
For fun, I'll include a case where the Rust standard library does this sort of thing on purpose. The signature of std::thread::scope looks like:
pub fn scope<'env, F, T>(f: F) -> T
where
F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T
The Scope that is provided to the user-defined function intentionally has its lifetimes tied in a knot to ensure it is only used in intended ways. This is not always the case since structs may be covariant or contravariant over their generic types, but Scope is defined to be invariant. Also, the only function that can be called on it is .spawn() which intentionally takes &'scope self as the self-parameter as well, ensuring that the reference does not have a shorter lifetime than what is given by scope.
Internally, the standard library contains this documentation (source):
Invariance over 'scope, to make sure 'scope cannot shrink, which is necessary for soundness.
Without invariance, this would compile fine but be unsound:
std::thread::scope(|s| {
s.spawn(|| {
let a = String::from("abcd");
s.spawn(|| println!("{a:?}")); // might run after `a` is dropped
});
});
Even if the lifetime of the reference is invariant with respect to itself, this still avoids many problems above because it uses an immutable reference and interior-mutability. If the parameter to .spawn() required &'scope mut self, then this would not work and run into the same problems above when trying to spawn more than one thread.
The issue isn't lexical lifetimes, and adding an explicit drop won't change the error. The issue is with the &'a mut Chain<'a>- that forces root to be borrowed for its entire life, rendering it useless after the borrow is dropped. As per the comment below, doing this with lifetimes is basically impossible. I would suggest using a box instead. Changing the struct to
pub enum Chain{
Root {
value: String,
},
Child {
parent: Box<Chain>,
},
}
and adjusting the other methods as necesary. Alternatively, using an Rc<RefCell<Chain>> if you want the original to remain usable without consuming self.
I am testing my understanding of lifetimes in Rust by explicitly annotating function signatures and I created an example that I am not sure I understand.
In this example, I am simulating the concept of sharing a book and turning a page within it. To do this I am using a single mutable reference which I pass to a borrow_and_read function that updates the curr_page field of a Book struct. My Book struct and main function look like:
#[derive(Debug)]
pub struct Book<'a> {
pub title: &'a str,
pub curr_page: Option<i32>,
pub page_count: i32,
}
fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
match a_book.curr_page {
Some(page) => a_book.curr_page = Some(page + 1),
None => a_book.curr_page = Some(0),
};
}
fn main() {
let mut the_book: Book = Book {
title: "The Book",
curr_page: None,
page_count: 104,
};
let a_book: &mut Book = &mut the_book;
borrow_and_read(a_book);
borrow_and_read(a_book);
observe_book(&*a_book);
}
pub fn observe_book<'a>(a_book: &'a Book<'a>) {
println!("Observing: {:?}", a_book);
}
(Playground)
For my first implementation of the borrow_and_read function, I let the compiler add annotations and everything compiled:
fn borrow_and_read(a_book: &mut Book) {
match a_book.curr_page {
Some(page) => a_book.curr_page = Some(page + 1),
None => a_book.curr_page = Some(0),
};
}
I then tried adding a single lifetime annotation specifying a lifetime for both the reference and the instance of the Book itself:
fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
match a_book.curr_page {
Some(page) => a_book.curr_page = Some(page + 1),
None => a_book.curr_page = Some(0),
};
}
This yielded the following errors:
error[E0499]: cannot borrow `*a_book` as mutable more than once at a time
--> src/main.rs:25:21
|
24 | borrow_and_read(a_book);
| ------ first mutable borrow occurs here
25 | borrow_and_read(a_book);
| ^^^^^^
| |
| second mutable borrow occurs here
| first borrow later used here
error[E0502]: cannot borrow `*a_book` as immutable because it is also borrowed as mutable
--> src/main.rs:27:18
|
24 | borrow_and_read(a_book);
| ------ mutable borrow occurs here
...
27 | observe_book(&*a_book);
| ^^^^^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here
After thinking through what I had initially tried, I decided it made sense to separate the lifetimes of the mutable reference to a Book and the instance of Book itself. I then came up with this:
fn borrow_and_read<'a, 'b>(a_book: &'a mut Book<'b>)
where 'b : 'a {
match a_book.curr_page {
Some(page) => a_book.curr_page = Some(page + 1),
None => a_book.curr_page = Some(0),
};
}
which does compile and output the expected results.
I am confused as to why my initial error message was that a_book was borrowed mutably more than once. I thought I would be ok passing around a single mutable reference since each usage of the reference understood that the reference was mutable. This thinking seems to be confirmed by the final implementation of my borrow_and_read function but I am not completely sure why specifying that the lifetime of the Book instance outlives the mutable reference with where 'b : 'a fixes my issue.
I am hoping to get a solid understanding of how using the same lifetime for both the mutable reference and Book instance yield the errors I got.
The problem with your original is that the lifetimes are too constrained. By making the borrow on the Book have the same length as the borrow on the book title ("The Book"), the mutable borrow is forced to last as long as the actual book itself, meaning that it can never be immutably borrowed.
Let's explore that. It'll be easier to examine your fixed version and then look at what the original does to constrain it.
fn borrow_and_read<'a, 'b>(a_book: &'a mut Book<'b>)
where 'b : 'a {
match a_book.curr_page {
Some(page) => a_book.curr_page = Some(page + 1),
None => a_book.curr_page = Some(0),
};
}
This function has two lifetime parameters: one for the book itself and one for the mutable borrow on the book. We also constrain 'b: 'a, meaning that any borrows with lifetime 'a are valid for no longer than borrows with lifetime 'b. This is actually redundant, since the compiler can see that anyway. By having an argument with type &'a mut Book<'b>, 'a already can't last longer than 'b.
Now let's look at main. We'll call the lifetime on the book itself 'book. We'll call the lifetime on the mutable borrow of the book 'mtb. Finally, we'll call the immutable borrow (at observe_book) 'imb. Let's see how long each lifetime has to last.
// Initialize `the_book`. 'book has to start before this.
// Mutably borrow `the_book`. 'mtb has to start here.
let a_book: &mut Book = &mut the_book;
// Use the mutable borrow. 'mtb has to still be valid.
borrow_and_read(a_book);
// Use the mutable borrow. 'mtb has to still be valid.
borrow_and_read(a_book);
// Deref the mutable borrow and reborrow immutably.
// 'imb has to start here, so 'mtb has to end here.
// 'imb is a reference to `the_book`, so 'book has to still be active.
observe_book(&*a_book);
// The variables are no longer needed, so any outstanding lifetimes can end here
// That means 'imb and 'book end here.
So the crux of the issue here is that with this setup, 'mtb has to end before 'book. Now let's look at the original version of the function.
fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
match a_book.curr_page {
Some(page) => a_book.curr_page = Some(page + 1),
None => a_book.curr_page = Some(0),
};
}
Now we only have one lifetime parameter, which forces the lifetime of the title and the lifetime of the mutable borrow to be the same. That means that 'mtb and 'book have to be the same. But we just showed that 'mtb has to end before 'book! So with that contradiction, the compiler gives us an error. I don't know the technical details of why the error is cannot borrow*a_bookas mutable more than once at a time, but I imagine that the compiler thinks of "usages" of a variable similarly to how we talk about lifetimes. Since 'book has to last until the call to observe_book and 'mtb is the same as 'book, it treats the usage of 'book as a usage of the mutable borrow. Again, I'm not completely sure about that. It might be worth filing an issue to see if the message can be improved.
I did actually lie a little bit above. While Rust doesn't do implicit type coercion, it does do lifetime coercion. Borrows with longer lifetimes can be coerced to borrows with shorter lifetimes. That ultimately doesn't matter too much here, but it's worth knowing about.
The title of the book, a string literal, has the type &'static str, where 'static is a special lifetime that lasts for the whole duration of the program. The data is embedded into the binary of the program itself. When we initialize the_book, it could have the type Book<'static>, but it could equally be coerced to Book<'book> for some shorter lifetime 'book. When we take the mutable borrow we're forced to have 'book: 'mtb, but we still have no other constraints.
When we call the one-parameter version of borrow_and_read, 'book and 'mtb have to both be coerced down to a shorter, common lifetime. (in this case, since 'book: 'mtb, 'mtb would work - and indeed, it's the longest lifetime that would work). With the two-parameter version, no coercion is necessary. 'book and 'mtb can be used as is.
Now when we deref a_book and reborrow it immutably, no mutable borrows can be active. That means that mtb and the shorter lifetime that both 'book and 'mtb were coerced to have to end. But a_book has lifetime 'book and we're using it, so 'book can't end. Hence the error.
With the two-parameter version, 'book wasn't coerced to a shorter lifetime, so it could continue.