Reputation: 4596
Consider this toy code:
struct X {
x: usize,
times_accessed: usize,
}
impl X {
fn get(&mut self) -> usize {
self.times_accessed = self.times_accessed + 1;
self.x
}
}
struct Y {
y: usize,
max_access: usize,
option_x: Option<X>,
}
impl Y {
fn get_x(&mut self) -> Option<usize> {
match self.option_x {
Some(ref mut some_x) => {
let result = some_x.get();
if some_x.times_accessed == self.max_access {
self.option_x = None;
}
Some(result)
}
None => {
println!("option_x is not initialized! try calling Y::init_x");
None
}
}
}
fn init_x(&mut self, x_val: usize, max_allowed_access: usize) {
self.max_access = max_allowed_access;
self.option_x = Some(X {
x: x_val,
times_accessed: 0,
});
}
}
fn main() {
println!("hello world!");
}
I haven't bothered using Y
in the main function, because the compiler doesn't need me to do that to point out that the borrow checker would not be satisfied with the implementation for Y::get_x
:
error[E0506]: cannot assign to `self.option_x` because it is borrowed
--> src/main.rs:26:21
|
22 | Some(ref mut some_x) => {
| -------------- borrow of `self.option_x` occurs here
...
26 | self.option_x = None;
| ^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `self.option_x` occurs here
I understand the issue from the borrow checker's perspective, and I can come up with a very simple fix: copy the value from some_x
into result
(am I not already doing that, after all, result isn't a reference, and usize
has the Copy
trait), and then "drop the reference" to some_x
(would it be using drop(some_x);
, so I can modify option_x
? A version of that is presented here, but still doesn't work:
fn get_x(&mut self) -> Option<usize> {
match self.option_x {
Some(ref mut some_x) => {
let result = some_x.get();
if some_x.times_accessed == self.max_access {
drop(some_x);
self.option_x = None;
}
Some(result)
}
None => {
println!("option_x is not initialized! try calling Y::init_x");
None
}
}
}
What should one do?
Upvotes: 0
Views: 70
Reputation: 430663
As the error message states, you are attempting to replace self.option_x
while you have a reference to something inside it:
Some(ref mut some_x) => {
let result = some_x.get();
if some_x.times_accessed == self.max_access {
self.option_x = None; // <---------
}
Some(result)
}
Any code after the highlighted point that accesses some_x
would get an invalid value. This is how programs crash, expose security holes, etc..
In a potential Rust future, a compiler that understands non-lexical lifetimes might realize that you don't use that value so the code is ok in the current state. Until then, you can take the entire value out of the Option
then put it back if your condition isn't met:
match self.option_x.take() {
Some(mut some_x) => {
let result = some_x.get();
if some_x.times_accessed != self.max_access {
self.option_x = Some(some_x);
}
Some(result)
}
See also:
Upvotes: 1