rust-lang / rust

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

Unclear error message when using recursive future with tokio::spawn #112175

Open krtab opened 1 year ago

krtab commented 1 year ago

Code

async fn tokio_fact(n: u32)  -> u32 {
    if n <= 1 {
        return n;
    } else {
        let jh = tokio::spawn(tokio_fact(n-1));
        jh.await.unwrap() * n
    }
}

Current output

error[E0391]: cycle detected when computing type of `tokio_fact::{opaque#0}`
   --> a/src/main.rs:99:33
    |
99  | async fn tokio_fact(n: u32)  -> u32 {
    |                                 ^^^
    |
note: ...which requires borrow-checking `tokio_fact`...
   --> a/src/main.rs:99:1
    |
99  | async fn tokio_fact(n: u32)  -> u32 {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...which requires promoting constants in MIR for `tokio_fact`...
   --> a/src/main.rs:99:1
    |
99  | async fn tokio_fact(n: u32)  -> u32 {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...which requires preparing `tokio_fact` for borrow checking...
   --> a/src/main.rs:99:1
    |
99  | async fn tokio_fact(n: u32)  -> u32 {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...which requires unsafety-checking `tokio_fact`...
   --> a/src/main.rs:99:1
    |
99  | async fn tokio_fact(n: u32)  -> u32 {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...which requires building MIR for `tokio_fact`...
   --> a/src/main.rs:99:1
    |
99  | async fn tokio_fact(n: u32)  -> u32 {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...which requires match-checking `tokio_fact`...
   --> a/src/main.rs:99:1
    |
99  | async fn tokio_fact(n: u32)  -> u32 {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...which requires building THIR for `tokio_fact`...
   --> a/src/main.rs:99:1
    |
99  | async fn tokio_fact(n: u32)  -> u32 {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...which requires type-checking `tokio_fact`...
   --> a/src/main.rs:103:18
    |
103 |         let jh = tokio::spawn(tokio_fact(n-1));
    |                  ^^^^^^^^^^^^
    = note: ...which requires evaluating trait selection obligation `tokio_fact::{opaque#0}: core::marker::Send`...
    = note: ...which again requires computing type of `tokio_fact::{opaque#0}`, completing the cycle

Desired output

I'm not sure what should be the proper error message. Because of the tokio::spawn this is not the same problem as in E0733.

The workaround often proposed by @Darksonn on various forums is to use a wrapping non-async function:

async fn tokio_fact_bis(n: u32)  -> u32 {
    if n <= 1 {
        return n;
    } else {
        fn tokio_fact_wrap(n : u32) -> JoinHandle<u32> {
            tokio::spawn(tokio_fact_bis(n))
        }
        let jh = tokio_fact_wrap(n-1);
        jh.await.unwrap() * n
    }
}

Rationale and extra context

No response

Other cases

No response

Anything else?

No response

tmandry commented 1 year ago

We have some logic to detect recursive async functions here and give a more helpful error message...

https://github.com/rust-lang/rust/blob/b049093560aa1c69face8c1893bd8acd99fff275/compiler/rustc_hir_analysis/src/check/check.rs#L240-L246

We should expand the logic to cover this case as well.

withoutboats commented 7 months ago

(NOT A CONTRIBUTION)

It seems like the bigger issue here is not the confusing error message but that this should not trigger any error at all: rustc should not need to recursively typeck the future because the future is not stored in its own state.