rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.61k stars 12.74k forks source link

Scoping rules for tail expressions leads to unexpected compiler error #69367

Open Sherlock-Holo opened 4 years ago

Sherlock-Holo commented 4 years ago

I tried this code: playground

use tokio::sync::{Mutex, RwLock};

struct InnerUser {
    file_handle: Mutex<u64>,
}

struct Foo(RwLock<InnerUser>);

impl Foo {
    async fn try_set_lock0(&self) -> Result<(), u32> {
        let guard = self.0.read().await;
        if let Ok(_) = guard.file_handle.try_lock() {
            Ok(())
        } else {
            Err(2)
        }
    }

    async fn try_set_lock1(&self) -> Result<(), u32> {
        let guard = self.0.read().await;
        let ret = if let Ok(_) = guard.file_handle.try_lock() {
            Ok(())
        } else {
            Err(2)
        };
        ret
    }
}

I expected to see this happen:

compile success

Instead, this happened: the first function report error

   Compiling playground v0.0.1 (/playground)
error[E0597]: `guard` does not live long enough
  --> src/lib.rs:12:24
   |
12 |         if let Ok(_) = guard.file_handle.try_lock() {
   |                        ^^^^^-----------------------
   |                        |
   |                        borrowed value does not live long enough
   |                        a temporary with access to the borrow is created here ...
...
17 |     }
   |     -
   |     |
   |     `guard` dropped here while still borrowed
   |     ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `std::result::Result<tokio::sync::mutex::MutexGuard<'_, u64>, tokio::sync::mutex::TryLockError>`
   |
   = note: The temporary is part of an expression at the end of a block. Consider forcing this temporary to be dropped sooner, before the block's local variables are dropped. For example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block.

error: aborting due to previous error

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

Meta

rustc --version --verbose:

rustc 1.43.0-nightly (8aa9d2014 2020-02-21)
binary: rustc
commit-hash: 8aa9d2014f4e5258f83b907e8431c59a33acdae7
commit-date: 2020-02-21
host: x86_64-unknown-linux-gnu
release: 1.43.0-nightly
LLVM version: 9.0

playground is stable 1.41.0
Polkaverse commented 4 years ago

@Sherlock-Holo Can you please elaborate, what you are trying to achieve here? As I can see you already handled the borrower check issue in the next function try_set_lock1() by storing the result in a local variable.

Sherlock-Holo commented 4 years ago

In try_set_lock0, the

if let Ok(_) = guard.file_handle.try_lock() {
    Ok(())
} else {
    Err(2)
}

expression will return a Result, this result doesn't contain any borrowed value, so that won't have any lifetime problem and it should compile success

tmandry commented 4 years ago

Confirmed that this is not specific to async-await: playground

@nikomatsakis said that this is a consequence of the scoping rules for tail expressions in the language, not something that can be fixed in the compiler. They also said it's possible we'd want to change this in an edition, but it's not clear how easy or hard that is.

nikomatsakis commented 4 years ago

@pnkfelix or @matthewjasper do you remember if we have an issue from NLL days regarding the scoping giving surprising errors? I feel like we should open a meta-issue.

TechHara commented 1 year ago

Hello, is there any update on this issue? I also encountered the same issue:

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);
        }
        // uncommenting any of the following will compile
        // ()
        // 0;
        // let completely_random_var = 0;
    }
}

fn main() {}

rust playground

What is interesting is that using while let expression in place of while also resolves the issue

--- before
+++ after
@@ -10,15 +10,10 @@
     if let Some(x) = root {
         queue.push(x);
     }
-    while !queue.is_empty() {
-        let x = queue.pop().unwrap();
+    while let Some(x) = queue.pop() {
         if let Some(next) = x.borrow().next.clone() {
             queue.push(next);
         }
-        // uncommenting any of the following will compile
-        // ()
-        // 0;
-        // let completely_random_var = 0;
     }
 }
TechHara commented 1 year ago

Actually, the compiler does already suggest adding ; to fix this. IMO, the original code should compile w/o error, and having ; is unnecessary. Is this behavior intended or to be fixed?