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
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;
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.
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.
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.