yoshuawuyts / futures-concurrency

Structured concurrency operations for async Rust
https://docs.rs/futures-concurrency
Apache License 2.0
413 stars 33 forks source link

ConcurrentStream: issues with `Send` and `Sync` bounds #195

Open sgued opened 1 month ago

sgued commented 1 month ago

There appears to be some invariance introduced by the IntoConcurrentStream and Map implementation that confuses the compiler for the Send and Sync autotraits

The following:

use futures_concurrency::concurrent_stream::{ConcurrentStream, IntoConcurrentStream};

fn assert_value_send_sync<T: Send + Sync>(_v: &T) {}

async fn iterative_method(context: &str) -> Vec<String> {
    let vec: Vec<u32> = Vec::new();
    let mut acc = Vec::new();
    for item in vec {
        acc.push(handle_item(&item, &context).await);
    }
    acc
}

async fn concurrent_method(context: &str) -> Vec<String> {
    let vec: Vec<u32> = Vec::new();
    vec.into_co_stream()
        .map(|d| async move { handle_item(&d, &*context).await })
        .collect()
        .await
}

async fn handle_item(item: &u32, context: &str) -> String {
    format!("{context}: {item}")
}

fn main() {
    let context = String::new();
    let iterative = async {
        iterative_method(&context).await;
    };
    assert_value_send_sync(&iterative);

    let concurrent = async {
        concurrent_method(&context).await;
    };
    assert_value_send_sync(&concurrent);
}

fails with the error:

rror[E0308]: mismatched types
  --> src/main.rs:36:5
   |
17 |         .map(|d| async move { handle_item(&d, &*context).await })
   |                  ----------
   |                  |
   |                  the expected `async` block
   |                  the found `async` block
...
36 |     assert_value_send_sync(&concurrent);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected `async` block `{async block@src/main.rs:17:18: 17:28}`
              found `async` block `{async block@src/main.rs:17:18: 17:28}`
   = note: no two async blocks, even if identical, have the same type
   = help: consider pinning your async block and casting it to a trait object
note: the lifetime requirement is introduced here
  --> src/main.rs:3:30
   |
3  | fn assert_value_send_sync<T: Send + Sync>(_v: &T) {}
   |                              ^^^^

error[E0308]: mismatched types
  --> src/main.rs:36:5
   |
17 |         .map(|d| async move { handle_item(&d, &*context).await })
   |                  ----------
   |                  |
   |                  the expected `async` block
   |                  the found `async` block
...
36 |     assert_value_send_sync(&concurrent);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected `async` block `{async block@src/main.rs:17:18: 17:28}`
              found `async` block `{async block@src/main.rs:17:18: 17:28}`
   = note: no two async blocks, even if identical, have the same type
   = help: consider pinning your async block and casting it to a trait object
note: the lifetime requirement is introduced here
  --> src/main.rs:3:37
   |
3  | fn assert_value_send_sync<T: Send + Sync>(_v: &T) {}
   |                                     ^^^^

For more information about this error, try `rustc --explain E0308`.
error: could not compile `future-concurrency-repro` (bin "future-concurrency-repro") due to 2 previous errors

I can't make a minimal reproducer, but I first encountered this error message, which I think it's the same issue. To me this suggests this might even be a bug in rustc. Even if it isn't a bug, the error message makes no sense and should be fixed.

error: implementation of `Send` is not general enough
   --> src/main.rs:348:34
    |
348 |         .route("/dashboard/:id", post(dashboard_post))
    |                                  ^^^^^^^^^^^^^^^^^^^^ implementation of `Send` is not general enough
    |
    = note: `Send` would have to be implemented for the type `&str`
    = note: ...but `Send` is actually implemented for the type `&'0 str`, for some specific lifetime `'0`
yoshuawuyts commented 1 month ago

Thanks for raising this! Yeah having a minimal reproducer for this would be helpful. I'm also a little short on time so diving into this is a little tricky right now for me too.

Minimizing this test case would actually be a really good contribution if someone wants to make one!