cell.borrow().clone() creates a Ref temporary and returns an owned value. The Ref should be droppable immediately afterclone() since the owned value doesn't borrow from it.
However, if let/match/match with binding/for extend the Ref lifetime to the entire construct, while if/while drop it after evaluating the expression.
| Construct | Ref behavior |
|---|---|
| if | Dropped |
| while | Dropped |
| for | Extended |
| match | Extended |
| if let | Extended |
| match with binding | Extended |
The attached code demonstrates this inconsistency. So my question is: Why is Ref from cell.borrow().clone() extended in if let/match/match with binding/for but dropped in if/while? Is this a known issue, a bug or an intended feature?
Edit: I tested this with Rust 1.89
Thanks!
use std::cell::RefCell;
fn main() {
println!("=== FOR LOOP (iterator expression) ===");
run_test("for loop", for_loop_case);
println!("\n=== IF STATEMENT (condition) ===");
run_test("if statement", if_case);
println!("\n=== MATCH STATEMENT (scrutinee, no binding) ===");
run_test("match statement", match_case);
println!("\n=== WHILE LOOP (condition) ===");
run_test("while loop", while_case);
println!("\n=== IF LET (scrutinee with binding) ===");
run_test("if let", if_let_case);
println!("\n=== MATCH WITH BINDING (scrutinee with binding) ===");
run_test("match with binding", match_binding_case);
}
fn run_test(name: &str, f: fn()) {
let result = std::panic::catch_unwind(f);
if result.is_err() {
println!(" {name}: PANICS (temporary extended)");
} else {
println!(" {name}: OK (temporary dropped)");
}
}
fn for_loop_case() {
let cell: RefCell<i32> = RefCell::new(3);
// cell.borrow() returns Ref<i32>
// .clone() returns OWNED i32
// Ref should be droppable, but is extended to entire loop
for _item in 0..cell.borrow().clone() {
*cell.borrow_mut() += 1;
}
}
fn if_case() {
let cell: RefCell<i32> = RefCell::new(3);
// cell.borrow() returns Ref<i32>
// .clone() returns OWNED i32
// Ref should be droppable after condition is evaluated
if cell.borrow().clone() > 0 {
*cell.borrow_mut() += 1;
}
}
fn match_case() {
let cell: RefCell<i32> = RefCell::new(3);
// cell.borrow() returns Ref<i32>
// .clone() returns OWNED i32
// Ref should be droppable after scrutinee is evaluated
let _ = match cell.borrow().clone() {
3 => { *cell.borrow_mut() += 1; 1 }
_ => 0
};
}
fn while_case() {
let cell: RefCell<i32> = RefCell::new(3);
// Condition is re-evaluated each iteration
// Ref should be dropped after each condition check
while cell.borrow().clone() < 5 {
*cell.borrow_mut() += 1;
}
}
fn if_let_case() {
let cell: RefCell<i32> = RefCell::new(3);
// cell.borrow() returns Ref<i32>
// .clone() returns OWNED i32, wrapped in Some()
// Ref should be droppable after clone
let _ = if let Some(_x) = Some(cell.borrow().clone()) {
*cell.borrow_mut() += 1;
1
} else {
0
};
}
fn match_binding_case() {
let cell: RefCell<i32> = RefCell::new(3);
// cell.borrow() returns Ref<i32>
// .clone() returns OWNED i32, wrapped in Some()
// Ref should be droppable after clone
let _ = match Some(cell.borrow().clone()) {
Some(_x) => { *cell.borrow_mut() += 1; 1 }
None => 0
};
}