# Futures Pre-RFC: \`async fn() -\> impl Future\`

**URL:** <https://internals.rust-lang.org/t/futures-pre-rfc-async-fn-impl-future/7203>\
**Category:** Uncategorized\
**Created:** [April 1, 2018, 10:12am UTC](https://internals.rust-lang.org/t/futures-pre-rfc-async-fn-impl-future/7203 "2018-04-01T10:12:41Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![Nemo157](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nemo157/32/11585_2.png) [@Nemo157](https://internals.rust-lang.org/u/Nemo157)\
**Post date:** [April 1, 2018, 10:12am UTC](https://internals.rust-lang.org/t/futures-pre-rfc-async-fn-impl-future/7203/1 "2018-04-01T10:12:41Z")

</div>

## Summary

Transition `#[async]` functions

from 

being written with a `Result` return type that gets rewritten inside the macro to `impl Future`, e.g.

```rust
#[async]
fn fetch_rust_lang(client: hyper::Client) -> io::Result<String>

```

to

being written with an `impl Future` or similar return type directly, e.g.

```rust
#[async]
fn fetch_rust_lang(client: hyper::Client) -> impl Future<Item=String, Error=io::Error>

```

Also support return types of `impl Stream`, `Box<Future>`, `impl StableFuture` etc. (potentially any `CoerceUnsized` smart pointer if possible?).

## Motivation

- Tie the written signature more directly to the final signature
- Reduce friction for migrating to `#[async]`
- Forward compatibility with nominal existential types
- Forward compatibility with `impl Trait` in trait
- Remove necessity for `#[async_stream]` and associated macros.
- More consistency with other languages

## Drawbacks

- The generators return type is not written down
- `impl Trait` requires specifying all lifetimes

See [the full RFC](https://github.com/Nemo157/futures-rfcs/blob/async-fn()--%3E-impl-Future/async-fn()--%3E-impl-Future.md) for expanded paragraphs on each of these motivations and drawbacks.

---

<div class="post-metadata">

**Author:** ![withoutboats](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/withoutboats/32/4560_2.png) [@withoutboats](https://internals.rust-lang.org/u/withoutboats)\
**Post date:** [April 2, 2018, 8:38am UTC](https://internals.rust-lang.org/t/futures-pre-rfc-async-fn-impl-future/7203/2 "2018-04-02T08:38:58Z")

</div>

You sort of gloss over how serious the drawback of the final point, which is the reason the RFC I’m drafting (to be put online later this week) does not take this approach.

If we make async functions return `impl Future`, we essentially eliminate lifetime elision from async functions. This is a major downside, and from my perspective it seems like it swamps any benefits from moving to `impl Future`.

(I know you know this @Nemo157, but for other readers) The reason I say we eliminate lifetime elision is that the way an `async fn` works, it returns immediately without evaluating, returning a future which will evaluate the body of the function as it runs. This means that the future needs to capture _all_ input lifetimes to the function. Our elision rules recognize that this is extremely rare, so the return type normally does not capture any input lifetimes except the lifetime of `self` in a method (and even then its `impl Future + '_` to achieve that).

* * *

@nikomatsakis discussed this issue with the C# team actually - who set the precedence of returning the “outer” return type. Their primary reason was that they allow some kind of polymorphism where you could return something other than a `Task<T>`. You sort of allude to this in referencing `async_stream` - figuring out how to make functions that evaluate to streams work is one of the hardest parts of this design.

However, I’m sort of uneasy about your proposal of how to handle this, because the interpretation of how `yield` works will depend on whether or not the return type implements `Future` or `Stream`. Tying the semantics of the inner thing to the trait implementations of the return type seems like a layering violation to me. And what happens if you return a type that implements both `Future` and `Stream`?

I think a better approach would be to have both async functions and generators, and by implication to have async generators, which are Streams. I posted this “matrix” in the other pre-RFC.

The other use cases for return type polymorphism I think are all going away. The distinction between `StableFuture` and `Future` is just because we don’t have arbitrary object safe self types yet, but we will soon and those two traits will be merged. The returning `Box<Future>` use case is misdesigned right now in my opinion - the main reason is to allow async fns in traits, but we need a design for `impl Trait` in traits (preferably with a possible upgrade to `Box<Trait>` in the object case).

So I don’t see this reason as really compelling. I also don’t think the learnability argument is very strong. First, because you are most likely just going to `await` this anyway. Second, because there’s learning friction in the other direction also - you `-> impl Future` but `return T`. Third, and most importantly, because for learning of the cost of losing elisions outweighs all of this.

---

<div class="post-metadata">

**Author:** ![Nemo157](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nemo157/32/11585_2.png) [@Nemo157](https://internals.rust-lang.org/u/Nemo157)\
**Post date:** [April 2, 2018, 10:16am UTC](https://internals.rust-lang.org/t/futures-pre-rfc-async-fn-impl-future/7203/3 "2018-04-02T10:16:57Z")

</div>

> [@withoutboats](#):
>
> You sort of gloss over how serious the drawback of the final point

Sorry that you feel I glossed over this, I definitely do feel the pain of this point, I really don't like that I had to write a function signature like

```rust
pub fn read_exact<'a, 'b: 'a, R: Read + 'a>(
    mut this: Pin<'a, R>,
    buf: &'b mut [u8],
) -> impl StableFuture<Item = (), Error = Error<R::Error>>
         + Captures<'a>
         + Captures<'b>

```

But, I still feel that this is better solved as part of `impl Trait` than just avoided in `async fn`. Even if `futures` is the only use case where the current `impl Trait` lifetime capture rules are not the best; there are always going to be some functions that are easier to write using combinators instead of `async fn`, these are likely to have the exact same problem of capturing arguments in their closures and having to use an `impl Future` return type with way too many lifetime bounds.

> [@](#):
>
> And what happens if you return a type that implements both `Future` and `Stream`?

In my current prototype (and, I believe, forced by the definition of `Future` and `Stream`) there's only one case (well, family of cases because of the error type) where the return value can implement both

```rust
for<E> impl Future<Item=(), Error=E> + Stream<Item=!, Error=E>

```

I would argue these two traits are isomorphic so there's no real problem being able to return a type that implements both.

> [@](#):
>
> I think a better approach would be to have both async functions and generators, and by implication to have async generators, which are Streams. I posted this “matrix” in the other pre-RFC.

This is a super-interesting idea, if you expand on it in your planned RFC I definitely can't wait to see it.

> [@](#):
>
> The other use cases for return type polymorphism I think are all going away.

One case you didn't mention is "nominal existential types", I have a set of traits I hope to eventually model using ATC and implement via nominal existential types, e.g. the nominal existential type + `impl Trait` in trait examples are actually split from one trait I want to be able to write:

```rust
pub trait CountDown {
  type Counting<'a>: Future<Item = (), Error = !> + 'a;

  fn start(&mut self, count: Duration) -> Self::Counting<'_>;
}

impl CountDown for Timer {
  abstract type Counting<'a>: Future<Item = (), Error = !> + 'a;

  #[async]
  fn start(&mut self, count: Duration) -> Self::Counting<'_> {
    ...
  }
}

```

I'm still not certain that I need this over just using raw `impl Trait` in trait support, but this allows for adding additional constraints on the returned future:

```rust
fn foo<C>(countdown: C)
where
    C: CountDown,
    <C as CountDown>::Counting: Send + Unpin,

```

I guess the oft-mentioned `typeof` operator might provide an alternative that works with just `impl Trait` in trait

```rust
fn foo<C>(countdown: C)
where
    C: CountDown,
    typeof(<C as CountDown>::start)::Output: Send + Unpin,

```

---

<div class="post-metadata">

**Author:** ![Nemo157](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nemo157/32/11585_2.png) [@Nemo157](https://internals.rust-lang.org/u/Nemo157)\
**Post date:** [April 2, 2018, 10:33am UTC](https://internals.rust-lang.org/t/futures-pre-rfc-async-fn-impl-future/7203/4 "2018-04-02T10:33:46Z")

</div>

Oh, one other point I forgot to mention, I’m still somewhat hopeful that it will be possible to have “hot” async functions that run until the first yield point. After my small amount of investigation into generator arguments I think making generators hot may actually be one way to get their first argument in nicely. This would have the benefit of allowing `async fn` to take arguments by reference that they don’t keep bound

```rust
#[async]
pub fn listen(addr: &MultiAddr, event_loop: &reactor::Handle)
-> impl Stream<Item=(Transport, MultiAddr), Error=io::Error>
{
    let addr = multiaddr_to_socketaddr(addr)?;
    let listener = TcpListener::bind(&addr, event_loop)?;
    #[async]
    for (transport, addr) in listener.incoming() {
        let transport = Transport(transport);
        let addr = MultiAddr::from(addr.ip()) + Segment::Tcp(addr.port());
        stream_yield!((transport, addr));
    }
    Ok(())
}

```

---

<div class="post-metadata">

**Author:** ![jmst](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jmst](https://internals.rust-lang.org/u/jmst)\
**Post date:** [April 2, 2018, 11:05am UTC](https://internals.rust-lang.org/t/futures-pre-rfc-async-fn-impl-future/7203/5 "2018-04-02T11:05:52Z")

</div>

Why not just use async blocks/closures for “hot” async functions?

That is, you just write a normal function that does whatever it wants and then returns an async block/closure.

Implicit “hotness” seems a poor choice since it makes the function’s behavior drastically change if you add an await at the beginning (let’s say you change it to read some data from an async database instead of having it being static), which seems like a bad thing.

---

<div class="post-metadata">

**Author:** ![Nemo157](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nemo157/32/11585_2.png) [@Nemo157](https://internals.rust-lang.org/u/Nemo157)\
**Post date:** [April 2, 2018, 11:28am UTC](https://internals.rust-lang.org/t/futures-pre-rfc-async-fn-impl-future/7203/6 "2018-04-02T11:28:26Z")

</div>

> [@jmst](#):
>
> Why not just use async blocks/closures for “hot” async functions?

That's what I do currently, there are two downsides:

1. Cannot push errors from the pre-async sections into the same error channel, the above function has to be `-> io::Result<impl Stream<...>>` to handle the possibility of the first two function calls failing. (Sometimes this is what you want to differentiate between problems during initialisation and when running, but when you don't expect to ever hit those error cases it's simpler to just merge the errors together).
2. An extra level of nesting.

> [@jmst](#):
>
> Implicit “hotness” seems a poor choice since it makes the function’s behavior drastically change if you add an await at the beginning (let’s say you change it to read some data from an async database instead of having it being static), which seems like a bad thing.

This is how C#'s `async` functions work, I never recall having any issues with them being implicitly hot. At least with Rust's `async` functions if you move a reference from one side of the initial yield point you'll get a lifetime error if that will break the current signature.

---

<div class="post-metadata">

**Author:** ![jmst](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jmst](https://internals.rust-lang.org/u/jmst)\
**Post date:** [April 2, 2018, 1:02pm UTC](https://internals.rust-lang.org/t/futures-pre-rfc-async-fn-impl-future/7203/7 "2018-04-02T13:02:42Z")

</div>

As for passing errors, the problem is that the Rust compiler doesn't let you return two different types with an impl Trait return type, and the fix is to add support for that by implicitly generating an enum (also, you can obviously work around that by doing the enum construction yourself)

But also differentiating between initialization and running errors probably makes sense if you have a distinct initialization phase.

> [@Nemo157](#):
>
> This is how C#'s async functions work, I never recall having any issues with them being implicitly hot

I always found it a dubious design since a function is supposed to be "async", yet it actually does an unknown amount of work on CPU beforehand, and when you implement one you always have this nagging thought that you should somehow address this.

And again I think adding an await!() in an async function body should not have non-local implications like making anything that follows no longer part of the "special pre-async code portion". If there's some initial code that is not async it should be clearly marked, which the "return async block" solution does without adding syntax.

---

<div class="post-metadata">

**Author:** ![Nemo157](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nemo157/32/11585_2.png) [@Nemo157](https://internals.rust-lang.org/u/Nemo157)\
**Post date:** [April 2, 2018, 2:39pm UTC](https://internals.rust-lang.org/t/futures-pre-rfc-async-fn-impl-future/7203/8 "2018-04-02T14:39:42Z")

</div>

> [@jmst](#):
>
> you can obviously work around that by doing the enum construction yourself

You're right, there is a possible way to write that function

```rust
pub fn listen(addr: &MultiAddr, event_loop: &reactor::Handle)
-> impl Stream<Item=(Transport, MultiAddr), Error=io::Error>
{
    stream::once(do catch {
        let addr = multiaddr_to_socketaddr(addr)?;
        let listener = TcpListener::bind(&addr, event_loop)?;
        Ok(listener)
    })
    .map(|listener| async_stream_block! {
        #[async]
        for (transport, addr) in listener.incoming() {
            let transport = Transport(transport);
            let addr = MultiAddr::from(addr.ip()) + Segment::Tcp(addr.port());
            stream_yield!((transport, addr));
        }
        Ok(())
    })
    .flatten()
}

```

But I find that super terrible to read and would prefer to just switch to taking the arguments by value and forcing users to clone them instead of doing that.

> [@jmst](#):
>
> I always found it a dubious design since a function is supposed to be “async”, yet it actually does an unknown amount of work on CPU beforehand, and when you implement one you always have this nagging thought that you should somehow address this.

Well, since `async` is an implementation detail you have no way of knowing whether it was used to define the function you're calling, so when you're calling someone else's function it always does an unknown amount of work on CPU beforehand. If you're using `async` yourself you should be avoiding CPU bound work anyway.

Also, does it _really_ matter that the work happened _before_ you called `resume` or after? Either way in 99% of cases it's all occurring on the same `Executor` anyway, the same amount of work will happen on the same set of threads. If you want to spawn a `Future` off onto a different `Executor` and you care about how much pre-yield work it might do you should be spawning it via `executor.spawn(future::lazy(|_| start(args)).boxed())` anyway.

> [@jmst](#):
>
> And again I think adding an await!() in an async function body should not have non-local implications like making anything that follows no longer part of the “special pre-async code portion”.

Maybe I've just become too inured from working with C# and JavaScripts `async`/`await` implementations, but I feel it's pretty obvious that `await` is a yield point that most definitely has non-local implications on lifetimes based on what variables are live across the yield point. At least with Rust the compiler will track that for you, in C# and JavaScript it's up to you to know whether a function returning a `Task`/`Promise` will have finished with its arguments after it returns or only once it's complete.

---

<div class="post-metadata">

**Author:** ![jmst](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jmst](https://internals.rust-lang.org/u/jmst)\
**Post date:** [April 2, 2018, 3:06pm UTC](https://internals.rust-lang.org/t/futures-pre-rfc-async-fn-impl-future/7203/9 "2018-04-02T15:06:39Z")

</div>

> [@Nemo157](#):
>
> Well, since async is an implementation detail you have no way of knowing whether it was used to define the function you’re calling

That's true, I guess I misstated by point a bit: what I meant is that if a function returns Task/Future, it is generally expected to do any non-trivial work in the Task/Future, but "hot-by-default" async functions break that expectation.

> [@](#):
>
> Also, does it really matter that the work happened before you called resume or after?

It does matter if the executor is a thread pool and you are spawning a bunch of futures in parallel (certainly a common thing in C#, although in Rust maybe you would use Rayon for that in some or most cases).

> [@](#):
>
> await is a yield point that most definitely has non-local implications on lifetimes

This is only true for the first "await" in "hot" async functions. Which is my argument against "hot" async functions.

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [March 25, 2019, 8:30am UTC](https://internals.rust-lang.org/t/futures-pre-rfc-async-fn-impl-future/7203/10 "2019-03-25T08:30:00Z")

</div>

Closed on March 25, 2019, 8:30am UTC

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
