Can't calling drop actively change the scope of the variable?

My code is here:

use std::future::Future;
use std::rc::Rc;

enum Error {}

#[derive(Default)]
struct Foo {
    rc: Rc<()>,
}

fn take_foo<'a>() -> &'a Foo { todo!() }

async fn bar() {}

async fn foo_guard<F, O>(f: F) -> Result<O, Error>
    where
        F: FnOnce(&Foo) -> Result<O, Error> {
    let foo = Foo::default();
    match f(&foo) {
        Ok(v) => {
            drop(foo);
            Ok(v)
        }
        Err(err) => {
            drop(foo);
            bar().await;
            Err(err)
        }
    }
}

fn block_on<F: Future + Send + Sync + 'static>(f: F) {}

pub fn run() {
    block_on(async {
        foo_guard(|foo| -> Result<bool, Error> {
            Ok(false)
        }).await;
    });
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error: future cannot be sent between threads safely
  --> src/lib.rs:35:14
   |
35 |       block_on(async {
   |  ______________^
36 | |         foo_guard(|foo| -> Result<bool, Error> {
37 | |             Ok(false)
38 | |         }).await;
39 | |     });
   | |_____^ future created by async block is not `Send`
   |
   = help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `Rc<()>`
note: future is not `Send` as this value is used across an await
  --> src/lib.rs:26:18
   |
18 |     let foo = Foo::default();
   |         --- has type `Foo` which is not `Send`
...
26 |             bar().await;
   |                  ^^^^^^ await occurs here, with `foo` maybe used later
...
30 | }
   | - `foo` is later dropped here
note: required by a bound in `block_on`
  --> src/lib.rs:32:25
   |
32 | fn block_on<F: Future + Send + Sync + 'static>(f: F) {}
   |                         ^^^^ required by this bound in `block_on`

error: future cannot be shared between threads safely
  --> src/lib.rs:35:14
   |
35 |       block_on(async {
   |  ______________^
36 | |         foo_guard(|foo| -> Result<bool, Error> {
37 | |             Ok(false)
38 | |         }).await;
39 | |     });
   | |_____^ future created by async block is not `Sync`
   |
   = help: within `impl Future<Output = ()>`, the trait `Sync` is not implemented for `Rc<()>`
note: future is not `Sync` as this value is used across an await
  --> src/lib.rs:26:18
   |
18 |     let foo = Foo::default();
   |         --- has type `Foo` which is not `Sync`
...
26 |             bar().await;
   |                  ^^^^^^ await occurs here, with `foo` maybe used later
...
30 | }
   | - `foo` is later dropped here
note: required by a bound in `block_on`
  --> src/lib.rs:32:32
   |
32 | fn block_on<F: Future + Send + Sync + 'static>(f: F) {}
   |                                ^^^^ required by this bound in `block_on`

error: could not compile `playground` due to 2 previous errors

I think that if a variable is dropped early, then it won't be available in later stages, then it shouldn't be considered to cross the .await point, but the compiler explains:

                 drop(foo);
                 bar().await;
   |                  ^^^^^^ await occurs here, with `foo` maybe used later

Is my understanding wrong? Are there any keywords to search for?

1 Like

You have several problems here. The error about not being Send is due to use of Rc, which is forbidden in multi-threaded async runtimes. Use Arc instead.

It doesn't matter that you're limiting scope of Rc. Your copy may have limited scope, but by nature of Rc there could be other instances elsewhere that you would be unsafely racing with.


BTW: Rust can't make anything live longer. Lifetimes don't tell Rust what to do, but instead describe what the code already does.

fn take_foo<'a>() -> &'a Foo { todo!() }

this function doesn't make sense. Lifetime here describes where Foo has been borrowed from, and the answer is: nowhere. There's no function argument to borrow it from. This function can't work for any lifetime other than 'static which describes globals/literals compiled into the program or leaked memory.

6 Likes

#70919 maybe. At least, it's wrong about telling you where the foo drops.

You can work around this particular example by never moving foo into the generated async structure.

4 Likes