Hello everyone,
I've run into a puzzling compilation error when implementing a trait method which returns impl Future<Output = Self> + Send
. If the implementation of this trait method calls a function, that returns Result<_, Box<dyn std::error::Error>>
, and handles both cases of the its result, using tokio::time::sleep
in the error branch causes a compilation error. And if this wouldn't be strange enough on its own, there is a difference between using match
and if let ...
which affects the error as well.
Below a few code snippets illustrate the problem I just tried to describe:
The basic setup is a function which returns a boxed error and a trait containing an async function with the Send
trait bound on its return type:
/// A function which returns a `Box<dyn std::error::Error>`.
async fn test() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
/// A trait with a method, which has a `Send` trait bound on its return type.
/// Removing this `Send` trait bound, makes all examples below compile, but
/// in my use case the trait is defined in another crate.
trait MyTrait {
fn my_func() -> impl Future<Output = Self> + Send;
}
Trying to implement this trait like shown below, causes a compilation error:
struct ThisFails;
impl MyTrait for ThisFails {
async fn my_func() -> Self {
// Call `test()` function and work with its result...
match test().await {
Ok(_) => {},
Err(_) => {
// commenting this out resolves the compile error
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
},
}
todo!()
}
}
As the comment states, removing tokio::time::sleep
makes the code compile.
Using the if let ...
syntax instead of a match
on the other hand, works fine and the code compiles:
struct ThisWorks;
impl MyTrait for ThisWorks {
async fn my_func() -> Self {
// Call `test()` function and work with its result...
if let Ok(_) = test().await {
} else {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
todo!()
}
}
But switching the order of the if let ...
branches, causes the compile error to occur again:
struct ThisFails2;
impl MyTrait for ThisFails2 {
async fn my_func() -> Self {
// Call `test()` function and work with its result...
if let Err(_) = test().await {
// commenting this out resolves the compile error
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
} else {
}
todo!()
}
}
In all cases the error text says:
error: future cannot be sent between threads safely
--> examples/weird-error/main.rs:34:5
|
34 | async fn my_func() -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `my_func` is not `Send`
|
= help: the trait `Send` is not implemented for `dyn std::error::Error`
note: future is not `Send` as this value is used across an await
--> examples/weird-error/main.rs:38:67
|
36 | if let Err(_) = test().await {
| ------------ has type `Result<(), Box<dyn std::error::Error>>` which is not `Send`
37 | // commenting this out resolves the compile error
38 | tokio::time::sleep(std::time::Duration::from_secs(1)).await;
| ^^^^^ await occurs here, with `test().await` maybe used later
note: required by a bound in `MyTrait::{synthetic#0}`
--> examples/weird-error/main.rs:10:50
|
10 | fn my_func() -> impl Future<Output = Self> + Send;
| ^^^^ required by this bound in `MyTrait::{synthetic#0}`
I've also prepared a Rust Playground for everyone who wants to run the code and see the error in action
My questions about this issue are:
- What does
tokio::time::sleep
do to make theFuture
returned by themy_func
implementation not to beSync
anymore? Is this special to thetokio::time::sleep
function or can other function calls cause the same behavior? - Is there a fundamental difference between
match
andif let
which causes them to have different effects in this case? If so, how does this explain their differences in combination withtokio::time::sleep
?
Thank you very much in advance for your help! I'm really looking forward to hearing what you'll say about this problem