Warning
This post was published 45 days ago. The information described in this article may have changed.
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:
tokio::time::sleep
do to make the Future
returned by the my_func
implementation not to be Sync
anymore? Is this special to the tokio::time::sleep
function or can other function calls cause the same behavior?match
and if let
which causes them to have different effects in this case? If so, how does this explain their differences in combination with tokio::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
3 posts - 3 participants
🏷️ rust_feed