Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange lifetime error when iterating over a BTreeSet asynchronously

I want the async block in the following code to implement Send (Playground):

use std::collections::BTreeSet;
use std::future::ready;

pub fn test<T: Sync>(set: &BTreeSet<T>) -> impl Send + '_ {
    async move {
        for _ in set {
            ready(()).await;
        }
    }
}

But it gives the following error:

   Compiling playground v0.0.1 (/playground)
error[E0311]: the parameter type `T` may not live long enough
 --> src/lib.rs:4:44
  |
4 | pub fn test<T: Sync>(set: &BTreeSet<T>) -> impl Send + '_ {
  |             --                             ^^^^^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds
  |             |
  |             help: consider adding an explicit lifetime bound...: `T: 'a +`

error: aborting due to previous error

error: could not compile `playground`

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

I don't understand the error at all. Adding a lifetime bound does not solve the problem (Playground), unless the added lifetime bound is 'static (Playground).

I tried replacing BTreeSet with Vec, VecDeque, LinkedList, HashSet, BinaryHeap. All compiled without error. What is so special about BTreeSet?

like image 748
alephalpha Avatar asked Jul 08 '21 08:07

alephalpha


Video Answer


1 Answers

It appears that the error is a bug in Rust -- async functions are fairly new and it seems that there are a number of issues with odd or incorrect compiler errors, particularly with generics. I think this may be issue #71058 or maybe issue #64552.

I find that often lifetime errors like this, just mean the compiler is saying "Help! I'm confused."

Here's an example of a gratuitous change, which I think is functionally the same:

use std::collections::BTreeSet;
use std::future::ready;

type ItemType = dyn Sync;

pub fn test<ItemType>(set: &BTreeSet<ItemType>) -> impl Send + '_ {
    async move {
        for _ in set {
            ready(()).await;
        }
    }
}

which produces an error which I think is closer to what is tripping up the compiler (but still incorrect) Playground:

error: future cannot be sent between threads safely
 --> src/lib.rs:6:52
  |
6 | pub fn test<ItemType>(set: &BTreeSet<ItemType>) -> impl Send + '_ {
  |                                                    ^^^^^^^^^^^^^^ future created by async block is not `Send`
  |
note: captured value is not `Send`
 --> src/lib.rs:8:18
  |
8 |         for _ in set {
  |                  ^^^ has type `&BTreeSet<ItemType>` which is not `Send`
help: consider restricting type parameter `ItemType`
  |
6 | pub fn test<ItemType: std::marker::Sync>(set: &BTreeSet<ItemType>) -> impl Send + '_ {
  |                     ^^^^^^^^^^^^^^^^^^^

The above Rust error says that the future is not Send which would be the case if the async closure captured a data structure that did not support Send. In this case, it captures BTreeSet which does support Send. Why this happens with BTreeSet and not Vec or one of the other data structures you mention is likely some tiny difference in the syntax of its implementation that is tripping up the compiler.

You created a nice minimal example, so not sure what you are trying to accomplish. Here's a workaround that might help:

use std::collections::BTreeSet;
use std::future::ready;
use std::vec::Vec;
use futures::future::join_all;

pub async fn test<T: Sync>(set: &BTreeSet<T>) -> impl Send + '_ {
    let mut future_list = Vec::new();
    for _ in set {
        let new_future = async move {
            ready(()).await;
        };
        future_list.push(new_future);
    };
    join_all(future_list).await
}
like image 124
Ultrasaurus Avatar answered Oct 17 '22 08:10

Ultrasaurus