impl-trait-utils
impl-trait-utils copied to clipboard
async function with default implementation won't compile
I have a trait such as this:
#[trait_variant::make(Test: Send)]
trait LocalTest {
async fn foo() -> i32 { 0 }
}
but if fails with an error:
|
3 | #[trait_variant::make(Test: Send)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `{integer}` is not a future
|
It looks like the body of the default impl is translated to the Test trait, unchanged.
If I instead I try:
async fn foo() -> i32 { std::future::ready(0) }
Then it looks like the Test works, but LocalTest fails with a somewhat opposite error:
5 | async fn foo() -> i32 { std::future::ready(0) }
| ^^^^^^^^^^^^^^^^^^^^^ expected `i32`, found `Ready<{integer}>`
I'm not sure if this is a limitation of this crate, if so I think it should be documented somewhere; or if it is an oversight in the implementation. Or maybe I'm missing something?
Then, just to see what would happen, I tried:
async fn foo() -> i32 { async move { 0 } }
And I got an ICE!
5 | async fn foo() -> i32 { async move { 0 } }
| ^^^^^^^^^^^^^^^^ expected `i32`, found `async` block
|
= note: expected type `i32`
found `async` block `{async block@src/main.rs:5:29: 5:45}`
error: internal compiler error: compiler/rustc_mir_build/src/build/mod.rs:655:72: impossible case reached
...
Also ran into this. I have a trait with this function and a default body:
async fn is_version_supported(&self, req: &str) -> miette::Result<bool> {
let version = self.get_version().await?;
Ok(VersionReq::parse(req).into_diagnostic()?.matches(&version))
}
This fails to compile with:
error[E0728]: `await` is only allowed inside `async` functions and blocks
--> nextgen/vcs/src/vcs.rs:75:42
|
75 | let version = self.get_version().await?;
| ^^^^^ only allowed inside `async` functions and blocks
Checking moon_deno_tool v0.1.0 (/crates/deno/tool)
Checking moon_system_platform v0.1.0 (/crates/system/platform)
Checking moon_app v0.1.0 (/nextgen/app)
error[E0277]: `Result<bool, _>` is not a future
--> nextgen/vcs/src/vcs.rs:9:1
|
9 | #[trait_variant::make(Vcs: Send)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Result<bool, _>` is not a future
|
= help: the trait `Future` is not implemented for `Result<bool, _>`
= note: Result<bool, _> must be a future or must implement `IntoFuture` to be awaited
When looking at the trait_variant compiled output, it makes sense that it would fail. I'm surprised this wasn't tested at all?
#[doc = r" Return true if the current binary version matches the provided requirement."]
fn is_version_supported(
&self,
req: &str,
) -> impl ::core::future::Future<Output = miette::Result<bool>> + Send {
let version = self.get_version().await?;
Ok(VersionReq::parse(req).into_diagnostic()?.matches(&version))
}
I almost had opened an issue with the same problem with title, "making a variant with panic!() in async fn's body causes a compile error"
==== The issue body ====
Adding #[trait_variant::make(Test: Send)] to the following no-error code causes an error
pub trait LocalTest {
async fn test(&self) -> String {
panic!()
}
}
rustc: `()` is not a future
the trait `std::future::Future` is not implemented for `()`
() must be a future or must implement `IntoFuture` to be awaited [E0277]
Macros that desugar async fn have to emit async { panic!() } as the body instead of just panic!() to avoid the error, though there are complex cases, like:
todo!orunimplemented!is used instead- the method has more statements above the
panic!() - (trivial) a function that returns
!is used instead.
As the user side, I cannot find a way to work around this, especially when using the trait_variant::make variant along with other macros that emit default implementation containing panic!().
Related: https://github.com/rust-lang/rust/issues/35121 https://github.com/rust-lang/rfcs/pull/1637 https://github.com/rust-lang/rust/issues/20021
It’s not silky smooth to use for me also. However, I could give a relatively silky example.
#[trait_variant::make(Test: Send)]
trait LocalTest {
// Below can not work (`Result<(), _>` is not a future)
// async fn foo() -> anyhow::Result<()> {
// Ok(())
// }
// Below can work
fn foo() -> impl core::future::Future<Output = anyhow::Result<()>> {
async { Ok(()) }
}
async fn bar() -> anyhow::Result<()>;
async fn baz() -> anyhow::Result<()>;
}
struct Foo;
impl LocalTest for Foo {
async fn foo() -> anyhow::Result<()> {
Ok(())
}
async fn bar() -> anyhow::Result<()> {
Ok(())
}
async fn baz() -> anyhow::Result<()> {
todo!()
}
}
struct Bar;
impl Test for Bar {
async fn foo() -> anyhow::Result<()> {
Ok(())
}
async fn bar() -> anyhow::Result<()> {
Ok(())
}
async fn baz() -> anyhow::Result<()> {
todo!()
}
}
To conclude,
- Cannot impl
LocalTestandTestfor the same struct. - When define the trait, only default impl shouldn't use
async fn, usefn foo() -> impl core::future::Future<Output = ... >with inner async block instead. - When impl trait, the
LocalTestandTestare completely the same.
I gave both PRs a shot in a code base where I encountered this issue which involved lifetimes and #28 worked while with #20 I two errors where the lifetime 'the_self_lt needed to outlive 'static and that the implementation of Send is not general enough.
I have not been successful at creating a simpler example so sry I can't provide one.
@AlvaroSierra I'm surprised #28 worked with your example, I couldn't get it to compile without adding where Self: Sync to the macro output.
@tmandry In my case, where the self is taken as &mut self, I guess Sync is not required due to the single mutable reference enforced by the compiler.