rust
rust copied to clipboard
Unable to use match with a borrowed value which will be mutated and returned within the match statement
When trying to mutate a value within a match statement which is borrowed by the statement and returned as the result of the function, the compiler fails with the error E0506.
I tried this code:
struct LazyItem {
item: Option<String>
}
impl LazyItem {
fn get_item(&mut self) -> &str {
match &self.item {
None => {
self.item = Some("Greetings".to_string());
let Some(content) = &self.item else { unreachable!() };
content
},
Some(content) => content,
}
}
}
fn main() {
let mut instance = LazyItem { item: None };
assert_eq!(instance.get_item(), "Greetings");
}
I expected to see this happen: That the code compiles and runs successfully.
Instead, this happened: The compilation failed with the following error:
error[E0506]: cannot assign to `self.item` because it is borrowed
--> src/main.rs:9:17
|
6 | fn get_item(&mut self) -> &str {
| - let's call the lifetime of this reference `'1`
7 | match &self.item {
| - ---------- `self.item` is borrowed here
| _________|
| |
8 | | None => {
9 | | self.item = Some("Greetings".to_string());
| | ^^^^^^^^^ `self.item` is assigned to here but it was already borrowed
10 | | let Some(content) = &self.item else { unreachable!() };
... |
13 | | Some(content) => content,
14 | | }
| |_________- returning this value requires that `self.item` is borrowed for `'1`
For more information about this error, try `rustc --explain E0506`.
Using if syntax the code compiles and runs successfully.
Code
struct LazyItem {
item: Option<String>
}
impl LazyItem {
fn get_item(&mut self) -> &str {
if let None = &self.item {
self.item = Some("Greetings".to_string());
let Some(content) = &self.item else { unreachable!() };
content
} else if let Some(content) = &self.item {
content
} else {
unreachable!()
}
}
}
fn main() {
let mut instance = LazyItem { item: None };
assert_eq!(instance.get_item(), "Greetings");
}
Meta
rustc --version --verbose:
rustc 1.72.0 (5680fa18f 2023-08-23)
binary: rustc
commit-hash: 5680fa18feaa87f3ff04063800aec256c3d4b4be
commit-date: 2023-08-23
host: x86_64-unknown-linux-gnu
release: 1.72.0
LLVM version: 16.0.5
Nigthly version with the same result:
rustc 1.74.0-nightly (4e78abb43 2023-08-28)
binary: rustc
commit-hash: 4e78abb437a0478d1f42115198ee45888e5330fd
commit-date: 2023-08-28
host: x86_64-unknown-linux-gnu
release: 1.74.0-nightly
LLVM version: 17.0.0
This is a known limitation of the borrow checker. It's fixed by Polonius (-Zpolonius).
Thanks. I am not sure how hard it would be to achieve, but wouldn't it be good to adapt the error message in order to refer to the Polonius project?
I had a similar issue, using ref rather than matching on reference fixes it.
What I'm really confused about is that matching on references isn't just a syntax sugar for ref. I'd expect the compiler to desugar the code first before doing any analysis.
Thank you. This is much more readable/intuitive than the if statement. None the less this issue should be resolved and apparently already is in the Polonius project (not implying that you do not want this to be solved).
Polonius is still unstable and forcing people to use nightly is not nice, so using ref is the best option.
Definitely. I did not want to say to use nightly, only that a fix exists but the underlying project is still in the works.
I had a similar issue, using ref rather than matching on reference fixes it.
As far as I know, each match arm may have a different binding mode (move, ref, ref mut).
If you borrow the scrutinee (&self.item), it's borrowed for the entire match expression and therefore you can't assign to it in the None arm.
If you turn the Some(ref content) => content into Some(content) => &content, you move self.item into the match expression since the body of the match doesn't influence the binding modes I think. And thus &content creates a temporary that gets dropped too early.
Also compiles successfully with -Zpolonius after the following change:
- let Some(content) = &self.item else { unreachable!() };
- content
+ self.item.as_ref().unwrap()
I could close this as a duplicate of #54663 since unreachable!() and return … are both diverging expressions. However, they're sufficiently dissimilar to warrant keeping this open, maybe :thinking:
I am not sure if those two are similar, since the issue #54663 has a problem with the borrow checker expanding behind the return and in this issue the borrow checker borrows the value even if there is nothing to borrow (None). But I am not that into the internals of Rust and do not now if both issues do have the same cause or if None is really not borrowable.