Annotation on generated Future

When a block or function is marked async the compiler generates a Future impl automatically. Is there any way to add annotations to it? For example to add derive.

I doubt this is possible, since derive macros operate on the definition of a type as a token tree, and there is no such lexical definition in the case of the unnameable Future created by an async block.

On nightly you can do this to create a nameable wrapper around an unnameable future:

#![feature(type_alias_impl_trait)]
type Fut = impl Future<Output = String> + SomeOtherTrait;
struct FutWrapper(Fut);

And at least in principle this allows you to put #[derive(Trait)] on the definition of FutWrapper. But I don't think this is good for much, it's not going to allow you to implement any nontrivial traits that the generated future didn't already satisfy automatically.

1 Like

It's not possible, and I don't see a workaround.

Types generated by async blocks aren't even normal Rust types (they can be self-referential when nothing else in the language can), and there's no way to work with their content.

You can't even do much by wrapping them in a newtype. You can't name the Future type yet, and even when you box the future inside a newtype, it still won't help. The boxed inner type won't be implementing any additional traits, so even when you put #[derive(Foo)] on the newtype, you'll only get an error that the inner future doesn't implement Foo.

1 Like

I wondered, why not define the impl using the name created by type_alias_impl_trait? The answer is: it's not considered a type defined by the current crate for coherence, even though it exists only due to a definition in the current crate:

#![feature(type_alias_impl_trait)]
type SpecificFuture = impl std::future::Future<Output = ()>;
async fn specific() {}
async fn returns_specific() -> impl Fn() -> SpecificFuture {
    specific
}
impl std::fmt::Display for SpecificFuture {
    fn fmt(f: &mut std::fmt::Formatter) {
        write!(f, "hello world");
    }
}
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
 --> src/lib.rs:7:1
  |
7 | impl std::fmt::Display for SpecificFuture {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^--------------
  | |                          |
  | |                          `impl Future` is not defined in the current crate
2 Likes

This is quite similar to RFC 2132: Copy Closures where the unnameable type that is generated through closure syntax didn't implement traits like Copy and Clone which you would normally trivially derive.

In that case, the solution was to make the type system generate the impl Clone for [closure@src/main.rs:2:19: 2:24] directly.

Is there a particular problem you are trying to solve here? If you explain more about the context we might be able to help find alternate solutions.

1 Like

To summarize the above: No. (and it's not easy or likely to change)

I am trying to explore treating futures as actual objects rather than just immediately calling await on them. So the most useful are clone, debug, Serialize etc.

I could also see being able to provide an implementation of Drop as very useful. (Though a wrapper can easily do that)

Ooh, that's an interesting idea!

I guess the thought never crossed my mind because I've always thought of a future as some bit of computation which hasn't yet completed. So the idea of cloning/printing/serializing a Future would be akin to cloning/printing/serializing a function's stack frame before the function has started.

Leaning on the closure analogy again, I think it would be weird if people could implement Drop directly on the type generated by an async function or expression.

I'm guessing the easiest/idiomatic way to implement this would be putting a let _guard = DropGuard::new() at the top of your async function and using its destructor for any necessary finalising... That said, there is no such thing as AsyncDrop (although Without Boats has written up some thought experiments) so you would need to be a bit creative if your finaliser/cleanup code needs async.

Future is like enum of closures capturing state inside parts of async fn. I suppose auto-derived Clone and Copy could be doable, since it can be done for closures.

However, async fn future can contain references (pointers) to itself as well as data outside of it, so it's going to be tricky to serialize. It's not possible in a general case.

Futures are supposed to be pinned to a stable address. You won't be able to deserialize back to the same address, so that adds a new twist on what pinned actually means.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.