Hello, I encountered a weird type error when attempting to use futures::future::FutureExt::map
on futures that I later feed into tokio::try_join!
(or futures::future::try_join
).
I tried to create a small example which causes the compile-time error:
use async_trait::async_trait;
use futures::future::FutureExt;
use std::borrow::Cow;
use std::io;
struct S {}
#[async_trait]
trait Foo: Sync + Send {
async fn foo(&self) -> io::Result<Cow<'_, ()>> {
println!("Executed foo");
Ok(Cow::Owned(()))
}
async fn bar<Other: Foo>(
&self,
other: &Other,
) -> io::Result<()> {
// The trivial case:
self.foo().await?;
other.foo().await?;
// Let's execute concurrently:
tokio::try_join!(
self.foo(),
other.foo()
)?;
// Now we try to map the io::Result, which also works:
self.foo().map(|x| x).await?;
other.foo().map(|x| x).await?;
// But this fails to compile:
// tokio::try_join!(
// self.foo().map(|x| x),
// other.foo().map(|x| x)
// )?;
// It doesn't seem to be Tokio related, as this also fails:
// futures::future::try_join(
// self.foo().map(|x| x),
// other.foo().map(|x| x)
// ).await?;
// But this works:
futures::future::try_join(
self.foo(),
other.foo()
).await?;
Ok(())
}
}
#[async_trait]
impl Foo for S {}
#[tokio::main]
async fn main() -> io::Result<()> {
let s1 = S {};
let s2 = S {};
s1.bar(&s2).await?;
Ok(())
}
The error I get, when adding the commented out code, is:
error: implementation of `FnOnce` is not general enough
--> src/main.rs:17:25
|
17 | ) -> io::Result<()> {
| _________________________^
18 | | // The trivial case:
19 | | self.foo().await?;
20 | | other.foo().await?;
... |
50 | | Ok(())
51 | | }
| |_____^ implementation of `FnOnce` is not general enough
|
= note: closure with signature `fn(Result<Cow<'0, ()>, std::io::Error>) -> Result<Cow<'_, ()>, std::io::Error>` must implement `FnOnce<(Result<Cow<'1, ()>, std::io::Error>,)>`, for any two lifetimes `'0` and `'1`...
= note: ...but it actually implements `FnOnce<(Result<Cow<'_, ()>, std::io::Error>,)>`
Apparently it has to do with the lifetime used for std::borrow::Cow
in the above example. If I change the return type of foo
to io::Result<()>
and return Ok(())
, then the problem doesn't occur.
I don't understand why .map(|x| x)
makes a difference here (which also works fine when I don't try to join the futures). Am I doing something wrong, or is this an error of the compiler? I assume it's me doing something wrong, but I don't see what it is.
To explain why I need this: In my productive code, the .map
isn't actually a no-op but will map error values (of concurrently executed methods) to different error values. In the Playground example above, I simplified the code to map x
to x
because it is sufficient to trigger/demonstrate the error.