# \[Pre-Pre-RFC\] Yet another discussion of if-let and while-let lifetime

**URL:** https://internals.rust-lang.org/t/pre-pre-rfc-yet-another-discussion-of-if-let-and-while-let-lifetime/16805
**Category:** language design
**Created:** [June 12, 2022, 5:20pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-yet-another-discussion-of-if-let-and-while-let-lifetime/16805 "2022-06-12T17:20:29Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![Neutron3529](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/neutron3529/32/6976_2.png) [@Neutron3529](https://internals.rust-lang.org/u/Neutron3529)
#### Post date: [June 12, 2022, 5:20pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-yet-another-discussion-of-if-let-and-while-let-lifetime/16805/1 "2022-06-12T17:20:29Z")

</div>

_This might be a breaking change, although it may not affect too much code._

There is always someone talking about the question that if-let and while-let lives too long.

> **[Drop semantics and temporary value lifetimes in while let](https://users.rust-lang.org/t/drop-semantics-and-temporary-value-lifetimes-in-while-let/30903)**
>
> Back when I was first learning rust, I filed this issue with the book. In short: while let Ok(job) = receiver.lock().unwrap().recv() { // do something time-consuming with job } looks nice, but if we want to not hold the lock during the body...

> **[While let borrow lifetime seems to be too long](https://users.rust-lang.org/t/while-let-borrow-lifetime-seems-to-be-too-long/49784)**
>
> Hello, By my reasoning, the two while let -loop headers in the program below should be equivalent, but the commented-out one causes a panic, as the borrowed value is not dropped by the time control enters the loop body. Or am I missing something...

> [@Lifetime in match if let and while let](https://internals.rust-lang.org/t/lifetime-in-match-if-let-and-while-let/14304):
>
> 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. } …

If we focus on if clause and if-let clause, a much strange things could happen:

```rust
// Works
if x.borrow().is_negative(){ *x.borrow_mut() = 0 }else{*x.borrow_mut() = 0} 
// Panics
if let false=x.borrow().is_negative(){ *x.borrow_mut() = 0 }else{*x.borrow_mut() = 0} 

```

It seems that, temporary variables' drop times should not be so inconsistent.

I met such problem a year ago, and finally found what I want. There are a bunch of people want to drop temporary variables as soon as possible, and others want to keep temporary variables until if-let and while-let finishes. Why not add some keyword to these two different action?

current:

```rust
let x = RefCell::new(0i32);
// Succeeds
match {let tmp = x.borrow().is_negative();tmp} {
    _ => { *x.borrow_mut() = 0 },
};
// Panics
match x.borrow().is_negative() {
    _ => { *x.borrow_mut() = 0 },
}
// Panics, too
if let flag=x.borrow().is_negative(){ *x.borrow_mut() = 0 }

```

method 1: add if move / while move / match move

```rust
// `move x=expr()` equals to `let x={let tmp=x.expr();tmp}` in if-let and while-let clauses
// `match move expr()` equals to `match {let tmp=x.expr();tmp}` in match-clauses.
match move x.borrow().is_negative() {
    _ => { *x.borrow_mut() = 0 },
}
if move flag=x.borrow().is_negative(){ *x.borrow_mut() = 0 }

```

method 2, change the default behavior of lf-let and while-let (also match), use a new keyword (e.g., ref,extend, lazy, etc.) to delay the drop execution.

```rust
if let flag=x.borrow().is_negative(){
    // x.borrow() drops as soon as possible, thus calling `x.borrow_mut()` is OK.
}
if lazy x.borrow().is_negative(){
    // x.borrow would only drop after the whole block is executed.
    // thus, it would panic by calling a x.borrow_mut().
}
match x.borrow().is_negative(){_=>{*x.borrow_mut() = 0}}//will not panic since `x.borrow()` dropped before the execution of `x.borrow_mut()`
match lazy x.borrow().is_negative(){_=>{
// here, x.borrow() still alive.
}}

```

I prefer method 2 since most of the cases, the temporary variables are unnecessary for programmers, but method 2 is a breaking change. If we do not want to modify a lot, method 1 might be acceptable.

Are there any disadvantages and limitations of such modification?

---

<div class="post-metadata">

### Author: ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)
#### Post date: [June 12, 2022, 11:05pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-yet-another-discussion-of-if-let-and-while-let-lifetime/16805/2 "2022-06-12T23:05:09Z")

</div>

(I'm speaking as myself and not for wg-grammar.)

`move $expr` isn't available\[1\] in the grammar, because move closures exist; if you wrote `match move || foo()` it would be ambiguous whether you mean `match (move || foo())` or `match move (|| foo())`.

The [clippy::significant\_drop\_in\_scrutinee](https://rust-lang.github.io/rust-clippy/master/#significant_drop_in_scrutinee) lint should lint against this. If it doesn't, open an issue (or PR — it's just adding an attribute to the type).

* * *

1. You can take the cop-out direction that `match move ||` is always a move closure, since temporary lifetime extension doesn't (?) apply when the scrutinee is a closure expression, but this is just saying the ambiguity doesn't matter, not actually addressing it.

---

<div class="post-metadata">

### Author: ![chrefr](https://avatars.discourse-cdn.com/v4/letter/c/e480ec/32.png) [@chrefr](https://internals.rust-lang.org/u/chrefr)
#### Post date: [June 12, 2022, 11:14pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-yet-another-discussion-of-if-let-and-while-let-lifetime/16805/3 "2022-06-12T23:14:27Z")

</div>

The fact that temporaries live for the entire statemment is very important for pattern matching and breaking that will not cause just slight breakage, I think. I am actually opposed to this proposal since I think the motivation isn't strong enough; the example with `is_negative()` is not convincing because you can just use `if`. Yes, symmetry is nice, but being able to introduce bindings to temporaries is much more important. I do acknowledge there are cases where `if` is not enough, but I think they are rare enough. Also, the main problem is finding the problem, since the fix is pretty trivial once you know it. So unless we're going to change the default (which I think is going to break lots of code), this is not going to help at all.

I think the solution is not a language construct, but more tools (linters etc., maybe even runtime tools) to find this problem, and more documentation about this.

---

<div class="post-metadata">

### Author: ![eggyal](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/eggyal/32/7947_2.png) [@eggyal](https://internals.rust-lang.org/u/eggyal)
#### Post date: [June 13, 2022, 1:42pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-yet-another-discussion-of-if-let-and-while-let-lifetime/16805/4 "2022-06-13T13:42:25Z")

</div>

I think, as @matthew-mcallister stated in [Lifetime in match if let and while let - #6 by matthew-mcallister](https://internals.rust-lang.org/t/lifetime-in-match-if-let-and-while-let/14304/6), this is a genuine footgun: [yet another issue](https://github.com/rust-lang/rust/issues/98052) was just filed about it this morning.

There are temporaries which need to live for the entire statement, and there are temporaries which are not. This pre-RFC proposes adding a keyword for the programmer to control dropping earlier, but I don't see that as warranted (one could always just bind the temporary within a block); nevertheless it should be possible to analytically determine whether the temporary can be dropped early (and, if so, do it). I've dug around a little, but can't find any fleshed out proposals for doing this, but I can see it being a useful enhancement to the language.

_Edit_: having read the linked threads a bit more thoroughly, and in particular [Lifetime in match if let and while let - #4 by CAD97](https://internals.rust-lang.org/t/lifetime-in-match-if-let-and-while-let/14304/4), I can see now that an automated analysis will not be able to make this determination and any such change could break existing code. It probably then is a no-go without some explicit language construct (which I still think is unnecessary).

---

<div class="post-metadata">

### Author: ![Neutron3529](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/neutron3529/32/6976_2.png) [@Neutron3529](https://internals.rust-lang.org/u/Neutron3529)
#### Post date: [June 13, 2022, 3:24pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-yet-another-discussion-of-if-let-and-while-let-lifetime/16805/5 "2022-06-13T15:24:17Z")

</div>

I once thought I wrote strong reasons that assist my opinion, but as the code

```rust
if let Some(x)=vec_mutex.lock().unwrap().get_mut()

```

failed to compile, I finally recognized that, it is not only something related to `if-let`, but also something to NLL.

think about this question: when should temporaries drop?

```rust
let x=vec_mutex.lock().unwrap().get_mut(0);//failed to compile
/*
rustc --edition 2021 test.rs -o test && ./test
error[E0716]: temporary value dropped while borrowed
 --> test.rs:4:11
  |
4 | let x=vec_mutex.lock().unwrap().get_mut(0);//failed to compile
  | ^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
  | |
  | creates a temporary which is freed while still in use
5 | println!("{}",x.unwrap());
  | - borrow later used here
  |
  = note: consider using a `let` binding to create a longer lived value

error: aborting due to previous error

For more information about this error, try `rustc --explain E0716`.
*/

```

This could be a old question.

If we could determine the correct drop time of `let-clauses`, we might have no difficult deal with if-let clauses.

But now, who against "drop variables after whole `if-let` clause" could only say:

```rust
// Works, documented in https://doc.rust-lang.org/stable/reference/destructors.html#temporary-scopes. thanks for @chrefr 's comment.
if x.borrow().is_negative(){ *x.borrow_mut() = 0 }else{*x.borrow_mut() = 0} 
// Panics, documented in https://doc.rust-lang.org/stable/reference/expressions.html#temporaries
if let false=x.borrow().is_negative(){ *x.borrow_mut() = 0 }else{*x.borrow_mut() = 0};

```

---

<div class="post-metadata">

### Author: ![chrefr](https://avatars.discourse-cdn.com/v4/letter/c/e480ec/32.png) [@chrefr](https://internals.rust-lang.org/u/chrefr)
#### Post date: [June 13, 2022, 11:52pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-yet-another-discussion-of-if-let-and-while-let-lifetime/16805/6 "2022-06-13T23:52:09Z")

</div>

It is documented:

[https://doc.rust-lang.org/stable/reference/destructors.html#temporary-scopes](https://doc.rust-lang.org/stable/reference/destructors.html#temporary-scopes)

---

<div class="post-metadata">

### Author: ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)
#### Post date: [September 11, 2022, 11:52pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-yet-another-discussion-of-if-let-and-while-let-lifetime/16805/7 "2022-09-11T23:52:09Z")

</div>

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