Reputation: 11
I'm encountering an issue with Rust's borrow checker that I believe is unnecessary, and I'm looking for a better understanding of why it occurs and how to work around it.
Consider the following code snippet:
struct Demo<'a>(&'a u64 /* , more fields ... */);
impl<'a> Demo<'a> {
fn foo(&mut self) -> &u64 {
// modify self ...
self.0
}
fn bar(&mut self) -> u64 {
let a: &u64 = self.foo();
let b: &u64 = self.foo();
*a + *b
}
}
When compiling this code, Rust emits the following error:
error[E0499]: cannot borrow `*self` as mutable more than once at a time
--> src/main.rs:11:23
|
10 | let a: &u64 = self.foo();
| ---- first mutable borrow occurs here
11 | let b: &u64 = self.foo();
| ^^^^ second mutable borrow occurs here
12 | *a + *b
| -- first borrow later used here
The compiler is preventing me from calling foo
twice on self, even though the returned references (a
and b
) are not used to mutate the state after they are obtained. It seems like the borrow checker is being overly conservative, as *a
does not modify any data and is only read from.
My questions are:
Why can't the Rust compiler recognize that *a
will not modify the Demo instance?
Is there a way to refactor this code to avoid the multiple mutable borrow error while maintaining the same logic?
Thank you.
Actrually I'm writing an interpreter:
pub type Scope<'a> = HashMap<&'a str, Rc<Value<'a>>>;
pub struct Environment<'a> {
scopes: Vec<Scope<'a>>,
}
struct Interpreter<'a> {
env: Environment<'a>,
}
fn run_expr(&mut self, expr: &Expression) -> Result<Rc<Value>, Error> {
match expr {
// ...
}
}
fn run_arithmetic(
&mut self,
op: &ArithmeticOperator,
lhs: &Expression,
rhs: &Expression,
) -> Result<Rc<Value>, Error> {
let lhs = self.run_expr(lhs)?;
let rhs = self.run_expr(rhs)?;
match (lhs.as_ref(), rhs.as_ref()) {
// ...
}
The Interpreter struct maintains an environment (env
) as a stack of hash maps, which stores variable bindings. self
is mutable because certain expressions can alter this environment.
However, this design leads to the issue when performing binary operations.
Upvotes: 0
Views: 65
Reputation: 42502
Why can't the Rust compiler recognize that *a will not modify the Demo instance?
Because the immutable borrow exists through the mutable borrow, they're stacked not parallel.
But it's also completely irrelevant, even if the language allowed downgrading borrows you couldn't call self.foo
again, for the simple reason that you'd still have an outstanding shared borrow as you try to acquire a unique borrow which is not allowed. It doesn't matter that *a
"will not modify the Demo instance", the second call to foo
could completely invalidate that reference, making it dangling, which is illegal.
Is there a way to refactor this code to avoid the multiple mutable borrow error while maintaining the same logic?
Don't return a reference. Or switch to inner mutability and runtime borrow checking.
Upvotes: 3