Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"temporary value dropped while borrowed" occurs with let-chains #100276

Closed
aznhe21 opened this issue Aug 8, 2022 · 11 comments · Fixed by #103034
Closed

"temporary value dropped while borrowed" occurs with let-chains #100276

aznhe21 opened this issue Aug 8, 2022 · 11 comments · Fixed by #103034
Assignees
Labels
C-bug Category: This is a bug. F-let_chains `#![feature(let_chains)]`

Comments

@aznhe21
Copy link

aznhe21 commented Aug 8, 2022

I tried this code:

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=02cc60332ebe5829791b841b96fa8c5b

fn let_chains(entry: std::io::Result<std::fs::DirEntry>) {
    if let Ok(entry) = entry
        && let Some(s) = entry.file_name().to_str()
        && s.contains("")
    {
        println!("will fail to compile");
    }
}

fn multiple_ifs(entry: std::io::Result<std::fs::DirEntry>) {
    if let Ok(entry) = entry {
        if let Some(s) = entry.file_name().to_str() {
            if s.contains("") {
                println!("will compile successfully");
            }
        }
    }
}

I expected to see this happen: both compiles successfully

Instead, this happened: let_chains fails to compile

error[E0716]: temporary value dropped while borrowed
 --> src/lib.rs:3:26
  |
3 |         && let Some(s) = entry.file_name().to_str()
  |                          ^^^^^^^^^^^^^^^^^        - temporary value is freed at the end of this statement
  |                          |
  |                          creates a temporary which is freed while still in use
4 |         && s.contains("")
  |            -------------- borrow later used here
  |
  = note: consider using a `let` binding to create a longer lived value

For more information about this error, try `rustc --explain E0716`.
error: could not compile `rust2` due to previous error

Meta

rustc --version --verbose:

rustc 1.65.0-nightly (d394408fb 2022-08-07)
binary: rustc
commit-hash: d394408fb38c4de61f765a3ed5189d2731a1da91
commit-date: 2022-08-07
host: x86_64-unknown-linux-gnu
release: 1.65.0-nightly
LLVM version: 14.0.6
@aznhe21 aznhe21 added the C-bug Category: This is a bug. label Aug 8, 2022
@Noratrieb
Copy link
Member

This may have something to do with #99852, as there is something about let_chain drops here as well, but I don't for sure.

@c410-f3r
Copy link
Contributor

c410-f3r commented Aug 9, 2022

The same behaviour happens with an older compiler version. Here goes an example that compiles with let_chains.

fn let_chains(entry: std::io::Result<std::fs::DirEntry>) {
    if let Ok(entry) = entry
        && let file_name = entry.file_name()
        && let Some(s) = file_name.to_str()
        && s.contains("")
    {
        println!("Ok");
    }
}

It is not up to me to decide if this behaviour is a blocker that will revert stabilization but in case of such event, I won't have enough free time to investigate and fix stuff in the near future.

@c410-f3r
Copy link
Contributor

c410-f3r commented Aug 9, 2022

Personally and despite the differences, I think that the let_chains version is "correct" and the nested version is surprisingly holding a reference of an owned structure (file_name()) without an explicit bind.

@Noratrieb
Copy link
Member

if let drops the scrutinee after the statement. Playground

@c410-f3r
Copy link
Contributor

cc @est31 who have been dealing with dropping stuff related to let else

@est31
Copy link
Member

est31 commented Aug 10, 2022

Some things that might be helpful to know:

@est31
Copy link
Member

est31 commented Aug 10, 2022

I've locally amended src/test/ui/let-else/let-else-drop-order-nested.rs of #99291 to add if let chains:

fn if_let_chain_let(v: u32) {
    macro_rules! if_let_chain {
        ($arg:pat, $exp:expr) => {
            if true && let $arg = $exp && ({
                print!("postfix,");
                true
            },).0 {
                print!("body,");
            } else {
                print!("else,");
            }
        };
    }
    nestings!(if_let_chain, "if let chain (let)", v);
    print!("\n  ref &:        ");
    if_let_chain!(0, &Droppy(v).0);

    print!("\n  ref mut &mut: ");
    if_let_chain!(0, &mut Droppy(v).0);

    println!("");
}

And this is the output:

v is 0 (match), construct is if let chain (let)
  vanilla:      drop,postfix,body,
  &:            drop,postfix,body,
  &mut:         drop,postfix,body,
  move:         drop,postfix,body,
  tuple:        drop,postfix,body,
  array:        drop,postfix,body,
  nested:       drop,inner,postfix,body,
  fn(this):     drop,postfix,body,
  fn(&self):    drop,postfix,body,
  ref &:        drop,postfix,body,
  ref mut &mut: drop,postfix,body,
[...]
v is 1 (mismatch), construct is if let chain (let)
  vanilla:      drop,else,
  &:            drop,else,
  &mut:         drop,else,
  move:         drop,else,
  tuple:        drop,else,
  array:        drop,else,
  nested:       drop,inner,else,
  fn(this):     drop,else,
  fn(&self):    drop,else,
  ref &:        drop,else,
  ref mut &mut: drop,else,

Which actually runs counter to what I wrote above, because it predrops. It seems that it depends on whether the let in the chain is preceded by a && or not: if you change the line with if true && let $arg = $exp && ({ to if let $arg = $exp && ({, removing the true &&, you get this:

v is 0 (match), construct is if let chain (let)
  vanilla:      postfix,body,drop,
  &:            postfix,body,drop,
  &mut:         postfix,body,drop,
  move:         postfix,body,drop,
  tuple:        postfix,body,drop,
  array:        postfix,body,drop,
  nested:       postfix,body,drop,inner,
  fn(this):     postfix,body,drop,
  fn(&self):    postfix,body,drop,
  ref &:        postfix,body,drop,
  ref mut &mut: postfix,body,drop,
[...]
v is 1 (mismatch), construct is if let chain (let)
  vanilla:      else,drop,
  &:            else,drop,
  &mut:         else,drop,
  move:         else,drop,
  tuple:        else,drop,
  array:        else,drop,
  nested:       else,drop,inner,
  fn(this):     else,drop,
  fn(&self):    else,drop,
  ref &:        else,drop,
  ref mut &mut: else,drop,

So that rule that I wrote above is not correct, but the underlying behaviour is more complicated. Hmmm...

@c410-f3r
Copy link
Contributor

Thanks @est31

@nathanwhit
Copy link
Member

@rustbot claim

@pnkfelix
Copy link
Member

@wesleywiser pointed this out to me.

It may be motivation to revert the stablization of let-chains on beta. Lets try to ensure we talk about this at Thursday triage

@rustbot label I-compiler-nominated

@rustbot rustbot added the I-compiler-nominated Nominated for discussion during a compiler team meeting. label Oct 18, 2022
@pnkfelix
Copy link
Member

False alarm; @wesleywiser pointed out to me that let-chains is already been marked unstable (on beta and nightly).

@rustbot label: -I-compiler-nominated

@rustbot rustbot removed the I-compiler-nominated Nominated for discussion during a compiler team meeting. label Oct 18, 2022
@bors bors closed this as completed in 48c5e0c Oct 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. F-let_chains `#![feature(let_chains)]`
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants