Struggle with Rust compiler — 4 Link to heading
Consider the code snippet below (rust playground):
Struggle with Rust compiler — order of drop Link to heading
Consider the code snippet below
use std::cell::RefCell;
use std::rc::Rc;
pub struct Node {
pub next: Option<Rc<RefCell<Node>>>,
}
pub fn foo(root: Option<Rc<RefCell<Node>>>) {
let mut queue = Vec::new();
if let Some(x) = root {
queue.push(x);
}
while !queue.is_empty() {
let x = queue.pop().unwrap();
if let Some(next) = x.borrow().next.clone() {
queue.push(next);
}
}
}
Do you seen any error in the code? I certainly don’t, yet the compiler says there is the dreadful lifetime issue with the code when run on Rust Playground:
Compiling playground v0.0.1 (/playground)
error[E0597]: `x` does not live long enough
--> src/main.rs:15:29
|
14 | let x = queue.pop().unwrap();
| - binding `x` declared here
15 | if let Some(next) = x.borrow().next.clone() {
| ^^^^^^^^^^
| |
| borrowed value does not live long enough
| a temporary with access to the borrow is created here ...
...
18 | }
| -
| |
| `x` dropped here while still borrowed
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, Node>`
|
help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped
|
17 | };
| +
For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` due to previous error
The error message certainly didn’t make sense and spent quite some time trying to figure it out. One solution I found was to assign x.borrow() to a new variable:
--- before
+++ after
@@ -12,7 +12,8 @@
}
while !queue.is_empty() {
let x = queue.pop().unwrap();
- if let Some(next) = x.borrow().next.clone() {
+ let borrowed = x.borrow();
+ if let Some(next) = borrowed.next.clone() {
queue.push(next);
}
}
Unfortunately, I really don’t understand why this is a fix — this looks more like an issue with the compiler itself rather than my code. After some time playing around, I found another fix, which again did not really make sense.
--- before
+++ after
@@ -10,8 +10,7 @@
if let Some(x) = root {
queue.push(x);
}
- while !queue.is_empty() {
- let x = queue.pop().unwrap();
+ if let Some(x) = queue.pop() {
if let Some(next) = x.borrow().next.clone() {
queue.push(next);
}
Furthermore, I found even more puzzling fix — adding a completely random unnecessary statement:
--- before
+++ after
@@ -15,6 +15,7 @@
if let Some(next) = x.borrow().next.clone() {
queue.push(next);
}
+ () // completely unnecessary statement
}
}
At this point, I went back to the original compiler suggestion, which I didn’t read carefully the first time.
help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped
|
17 | };
| +
For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` due to previous error
This suggestion looks similar to my last fix, and indeed practically any extra statement seems to fix the issue, though the reason is still not too clear. In any case, I think this is probably due to some glitch in the compiler and should be fixed. The good news is that the compiler did suggest a fix, although it didn’t make much sense.
So, the lessons are
- Accept the fact that Rust is still evolving and the compiler is not perfect.
- When the compiler suggests a fix, at least give it a try even if it looks complete non-sense.
Update: I feel like now I understand a bit more on why the compiler error occurs. Here is minimal reproduction of the issue:
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let x = Rc::new(RefCell::new(Some(1)));
if let Some(i) = x.borrow().as_ref() {
println!("{i}");
}
}
error[E0597]: `x` does not live long enough
--> src/main.rs:6:22
|
5 | let x = Rc::new(RefCell::new(Some(1)));
| - binding `x` declared here
6 | if let Some(i) = x.borrow().as_ref() {
| ^^^^^^^^^^
| |
| borrowed value does not live long enough
| a temporary with access to the borrow is created here ...
...
9 | }
| -
| |
| `x` dropped here while still borrowed
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, Option<i32>>`
|
help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped
|
8 | };
| +
For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` ("playground") due to previous error
I think this is what’s happening. x.borrow() creates a Ref<_> object, which the Rust compiler calls it a temporary. This temporary object is held by the Rust compiler in the if-let scope. At the same time, we have the variable x, which is what the temporary refers to. For some reason, Rust compiler is dropping x before dropping the temporary object, which is quite counter-intuitive, but I guess there must be a reason. That is why the compiler is complaining that the borrowed value, i.e., x does not live long enough compared to the temporary object.
By appending any statement, such as a semicolon, after the if-let block, we extend the lifetime of x, longer than the temporary. With this, the compiler no longer complains.
Now the question is, why is the variable drop order reversed? What is it that requires x to be dropped before the temporary if there is extra trailing statement after the if-let block? It may be related to this issue.