Converting`async fn` in public traits to `impl Future<Output = Result<(), Box<dyn Error>>> + Send;`

Originally, I have Playground 1

pub trait Import {
    async fn sql_import() -> Result<(), Box<dyn Error>>;

    async fn import() -> Result<(), Box<dyn Error>>
    where
        Self: DeserializeOwned,
    {
        Self::sql_import().await?;

        Ok(())
    }
}

And of course I got a warning about the two async fns in the trait and suggested to do

-     async fn sql_import() -> Result<(), Box<dyn Error>>;
+     fn sql_import() -> impl Future<Output = Result<(), Box<dyn Error>>> + Send;

If I follow the instructions Playground 2, I got an error

error: future cannot be sent between threads safely
  --> src/lib.rs:28:5
   |
28 |     async fn sql_import() -> Result<(), Box<dyn Error>> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `sql_import` is not `Send`
   |
   = help: within `IntoChunks<std::ops::Range<i32>>`, the trait `Sync` is not implemented for `RefCell<GroupInner<usize, Range<i32>, ChunkIndex>>`
   = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` instead
note: future is not `Send` as this value is used across an await
  --> src/lib.rs:30:36
   |
29 |         for _chunk in (0..2).chunks(1).into_iter() {
   |                       ---------------------------- has type `itertools::Chunks<'_, std::ops::Range<i32>>` which is not `Send`
30 |             execute_unprepared("").await?;
   |                                    ^^^^^ await occurs here, with `(0..2).chunks(1).into_iter()` maybe used later
note: required by a bound in `Import::sql_import::{anon_assoc#0}`
  --> src/lib.rs:12:88
   |
12 |     fn sql_import() -> impl std::future::Future<Output = Result<(), Box<dyn Error>>> + Send;

It turns out adding Send(suggested by the compilter) to the return causes troubles.

Questions

  1. Is async fn sql_import() -> Result<(), Box<dyn Error>>; the same as
    a) fn sql_import() -> impl Future<Output = Result<(), Box<dyn Error>>>; or
    b) fn sql_import() -> impl Future<Output = Result<(), Box<dyn Error>>> + Send;
  2. I don't understand why adding Send suddenly breaks, i.e. I cannot comprehend what the compiler tries to tell me. Why is it related to itertools::Chunks<..>?

When an async fn is declared in a trait, it is (a).

The implementation of itertools::Chunks uses RefCell to implement this part of its behavior:

it only buffers if several chunk iterators are alive at the same time.

This means that, if you want your async function future to be Send, you must not hold a Chunks or Chunk iterator (each of which borrows the IntoChunks returned by .chunks()) across an await point. Since your iterator is just a numeric range, I would suggest using arithmetic instead of .chunks() to compute your sub-ranges — it will be much more efficient, and not cause any Send problems.

1 Like

EDIT:

I overlooked that the question was about async function in trait declaration, my answer mainly focused on async fn implementations, so it might not accurately answer the original question.

END of EDIT

the answer to this question is unexpectedly complicated: neither, or, it depends.

auto traits like Send are, well, "auto", meaning, the Future generated by the compiler may or may not implement Send, depends on what went inside it. this is different from an impl Future opaque return type, where you have to explicitly declare the trait bounds. CORRECTION: impl Trait opaque types also leak auto traits implementations. thanks to @quinedot for the correction.

note, this happens not just for synthetic types like the Future of an async function. in the following example, you don't need to derive or blanket implement Send, but the compiler knows when it does or does not implement `Send:

struct Wrapper<T>(T);

// this is completely redundent: `Send` is auto trait
// you can comment out this line, nothing will change
unsafe impl<T: Send> Send for Wrapper<T> {}

fn require_send(_: impl Send) {}

fn main() {
	require_send(Wrapper(())); //<---  ok, `()` is `Send`
	require_send(Wrapper(&42i32)); //<---  ok, `&i32` is `Send`
	require_send(Wrapper(&raw const 42i32)); //<---  error: `*const i32` is not `Send`
}

everything lives across await points must be saved as part of the Future. when you have an await point inside a loop, the iterator of the loop became a field of the annonymous Future type, which happens to be itertools::Chunks. and because it is not Send, it makes the Future not Send, which violates the signature of the function.

I'm not sure exactly what you mean, but the two signatures do behave the same here:

async fn sql_import() -> Result<(), Box<dyn Error>>
fn sql_import() -> impl Future<Output = Result<(), Box<dyn Error>>>

Auto-traits leak through opaque types and the spelling of the signature doesn't change that. It's not an async fn specific behavior.

With dyn Future and such, auto-traits are explicit (unless they are a supertrait). Perhaps that is what you had in mind.

1 Like

Is it a mistake for the compiler to suggest using

fn sql_import() -> impl Future<Output = Result<(), Box<dyn Error>>> + Send;

instead, it should have suggested the following, without the Send trait

fn sql_import() -> impl Future<Output = Result<(), Box<dyn Error>>>;

Is it a mistake for the compiler to suggest using

The compiler is not telling you that you should add Send. It is telling you that you can “add any desired bounds such as Send”. It is up to you to decide whether you should have that bound or not, based on what your particular situation requires.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.