Lifetime in match if let and while let

I am very surprising found that, a temp variable only drop AFTER a WHOLE sentences is executed.


fn dead_lock_2() {// originally published in a Chinese forum https://rustcc.cn/article?id=3f446fab-1f4b-4d3f-9240-95b673bf5062
    let vec_mutex = Mutex::new(vec![1,2,3]);
    while let Some(num) = { vec_mutex.lock().unwrap().pop() } {
        if num == 2 {
            vec_mutex.lock().unwrap().push(4);// could not acquire the lock since `vec_mutex.lock()` in while expr is not dropped.
        }
        println!("got {}", num);
    }
}

I am very surprising to find that, many problem (include this one) could be solved by changing the expr to (||expr)() or {let tmp=expr;tmp}--most of the cases, we do not need a &, or a &mut I am really curious about that, why the default setting is drop the temp variables after an expression is executed, and why the behavior of (||expr)() or {let tmp=expr;tmp} could not be the default setting.

is it always better using (||expr)() or {let tmp=expr;tmp} than expr? if not, could anyone provide me a example?

If {let tmp=expr;tmp} always compiles when expr compiles, why not using the behavior of {let tmp=expr;tmp} instead of the old one?

1 Like

There are some links and discussion about this on the users forum, here:

1 Like

Thank you for your useful link.

that link suggests a better way dealing with if-let / while-let / match (just as what I want to do):

while let Some(x)={let tmp=expr.calling_some_method_that_create_temporary_variables;tmp}{
    ...
}

But, both the reply and relative link do not show when {let tmp=expr.calling_some_method_that_create_temporary_variables;tmp} fails to compile but a single expr.calling_some_method_that_create_temporary_variables compiles.

If {let tmp=expr.calling_some_method_that_create_temporary_variables;tmp} is always better, why not rust using this behavior in the next version?

There are cases where temporary lifetime extension is required for the code to compile, when you take a reference to a temporary. (Playground link)

Almost more importantly, it changes the drop timing, and the drop timing is observable in many ways. Consider a type that changes some global atomic on Drop. Changing the drop timing would change the result of your code.

At the most extreme, consider that the temporary holds some lock, releases it on drop, and you have some unsafe code that assumes that the lock is held. Changing the drop timing would thus silently make this code UB which was valid before.

4 Likes

Heh. Last week I was trying to puzzle out the interaction of this rule with the drop order of lazy boolean expressions w/ @Manishearth. I think he filed a bug against the reference because of confusing text, and I wonder if this is worth clarifying, too...

1 Like

I just discovered this behavior yesterday and then opened (and closed) an issue. It's a genuine footgun, and it makes using both Mutex and RefCell slightly harder, as if they weren't hard enough!

Consider this snippet:

let x = RefCell::new(0i32);

// Panics
match x.borrow().is_negative() {
    _ => { *x.borrow_mut() = 0 },
}

// Succeeds
let tmp = x.borrow().is_negative();
match tmp {
    _ => { *x.borrow_mut() = 0 },
}

The former match statement panics while the latter does not, but I suspect very few Rust users would have guessed this outcome without already knowing the answer. It violates the substitution principle, which says that simply assigning to an intermediate variable should produce an equivalent program.

Lifetime extension is useful and works well when assigning a temporary reference to a variable, but the issue here is that match statements are activating lifetime extension all the time and not just when necessary. Imagine we had this kind of desugaring going on:

// Before
match <expression> { ... }
// After
{
    let tmp = <expression>;
    match tmp { ... }
}

Then, the regular rules for activating lifetime extension would be used rather than having match statements be a weird exception where it's forced on at all times.

It's a breaking change, but I think if someone wants a temporary to live longer, they should be explicit about it and assign it to a variable instead of relying on a hard-to-discover rule.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.