18

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?


Update: This seems to be fixed in Rust 1.66.0. The code now compiles without error.

I still don't know the reason of this error and how it was fixed.

alephalpha
  • 560
  • 5
  • 13
  • 5
    Weird... Wonder if its worth creating a Rust-lang issue for. Even if there is a reason for it, the error message is unhelpful. – kmdreko Jul 09 '21 at 00:22
  • 1
    I agree, that should be looked at. It is worth noting that the iterator does not even have to be used to cause this behaviour - [Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3ca35174e4e50a85f804b86ab35ae221). – HHK Jul 09 '21 at 10:50
  • 1
    I don't have much of an idea myself, but i noticed that `BTreeSet` implementation of `IntoIterator` creates a new `IntoIter {iter: self.map.into_iter() }` structure (https://doc.rust-lang.org/src/alloc/collections/btree/set.rs.html#1077) , unlike implementations for `Vec`, `BinaryHeap`, etc. I don't fully understand it yet, but something about referencing `self.map` might be here at play. – somnium Jul 10 '21 at 16:54
  • 1
    @somnium `self.map` is used because `BTreeSet` is a wrapped `BTreeMap`. `BTreeMap` has the same problem, for both generic keys ([Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3b8f9678cf8b59fa62c16efe33ee17e3)) and generic values ([Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=40e0ad23060a36fead3bc6eea95c94fe)). – alephalpha Jul 11 '21 at 04:44

1 Answers1

1

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
}
Ultrasaurus
  • 3,031
  • 2
  • 33
  • 52
  • 1
    It could well be a compiler bug, but the question specifically asks why it happens with `BTreeSet`, but not with `Vec` or `HashSet`. I obviously don't speak for the OP but an answer that doesn't at least attempt to cover that is IMHO incomplete. – user4815162342 Jul 16 '21 at 18:15
  • Yes, it is incomplete. I did actually look into that, but when I discovered many bug reports in this area, I decided not to look into it further myself. I left an answer to be helpful, and am not bothered if it does not merit a bounty. I attempted to illustrate how compiler bugs can be triggered by somewhat arbitrary syntax differences, and also supply a workaround. – Ultrasaurus Jul 17 '21 at 00:41