rust-lang/rust

Tracking Issue for async_fn_in_trait, return_position_impl_trait_in_trait

nikomatsakis opened this issue ยท 26 comments

  • This is a tracking issue for the RFC "static async fn in traits" (rust-lang/rfcs#3185) and "Return position impl Trait in traits" (rust-lang/rfcs#3425).
  • The feature gate for async fn in traits is #![feature(async_fn_in_trait)].
  • The feature gate for return position impl Trait in traits is #![feature(return_position_impl_trait_in_trait)].
  • This feature is developed as part of the async fundamentals initiative -- see that website for more information!

About tracking issues

Tracking issues are used to record the overall progress of implementation.
They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

Steps

Unresolved Questions

Async fn in trait

Return position impl Trait in trait

  • Should we stabilize this feature together with async fn to mitigate hazards of writing a trait that is not forwards-compatible with its desugaring?
  • Resolution of #112194
    • Note that this might impact the desugaring, as described in this comment.
  • Should we limit the legal positions for impl Trait to positions that are nameable using upcoming features like return-type notation (RTN)? (See this comment for an example.)

Implementation history

Async fn in trait

F-async_fn_in_trait Static async fn in traits

Return position impl Trait in trait

F-return_position_impl_trait_in_trait `#![feature(return_position_impl_trait_in_trait)]`

Several of these include code specific to async fn in trait.

May I take a go at this?

Currently trying to take a stab at this. @rustbot claim

@LeSeulArtichaut actually, @spastorino has been looking into doing various refactorings to make this easier, you should probably synchronize with him, I'm not sure what state we are in.

Ohh, unsure why I've never assigned this to myself. I've been working on this since a while, and I've implemented RPITIT and async fns in traits support partially. At some point, we've realized that we wanted to change the design of the solution and started doing some refactors on RPITs.
Anyway, I've already talked with @LeSeulArtichaut, and I'd be happy to share some work with them about this feature or about any other feature โค๏ธ

@rustbot release-assignment

It seems this has been asleep for a while, is it blocked? is it dead? Does it need more love?

@fakedrake we are working on this thing, there's some support work being done by my RPIT Refactor and by RPITIT PR that just landed. You can see discussions on https://rust-lang.zulipchat.com/#narrow/stream/330606-wg-async.2Fasync-fn-in-trait-impl

Found a compiler crash when using this experimental feature: #104183

I tried (a second time) to use this in axum, and while it doesn't allow making the traits less flexible (allowing non-Send futures) due to #103854, it does work: tokio-rs/axum@b92fe41

However, I had to add a lot of 'static bounds to lifetime parameters and didn't understand why. If anybody can shed some light on that, that would be neat.

I am trying out this feature to replace the async-trait crate, and encountered an inconsistency together with the unstable specialization feature. I can't figure out whether the problem lies in here or in specialization (I did overuse the specialization feature a bit).

Here are two minimal examples comparing the same specialization logic with async-trait and async_fn_in_trait:

async-trait vs async_fn_in_trait

Edit: same logic without async, only specialization

I found async-trait crate can't be used to code in rustc 1.65.0(stable),but i don't know how to replace it.now,how will we write async trait

I found async-trait crate can't be used to code in rustc 1.65.0(stable),but i don't know how to replace it.now,how will we write async trait

Everything work fine with:

โœ— rustc --version
rustc 1.65.0 (897e37553 2022-11-02)

problem in somethere else.

I found async-trait crate can't be used to code in rustc 1.65.0(stable),but i don't know how to replace it.now,how will we write async trait

Everything work fine with:

โœ— rustc --version
rustc 1.65.0 (897e37553 2022-11-02)

problem in somethere else.

thanks,i will retry to check it

I found async-trait crate can't be used to code in rustc 1.65.0(stable),but i don't know how to replace it.now,how will we write async trait

Everything work fine with:

โœ— rustc --version
rustc 1.65.0 (897e37553 2022-11-02)

problem in somethere else.

I found that everything was OK after restarting the machine

That looks like #104908

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=a209ada19b11839fcf469805eaed49e5

async_fn_in_trait appears to break normal default implementation behavior.

@thorjelly you should probably report an issue for things like this, rather than commenting on the tracking task. in this case, I think it's a duplicate of #107002, which has a PR open to fix it.

Plaes, someone clarify what's the statis with dyn Traits now?

I've read here: https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html

Limitation: Dynamic dispatch
There's one final limitation: You can't call an async fn with a dyn Trait. Designs to support this exist4, but are in the earlier stages. If you need dynamic dispatch from a trait, you're better off using the async_trait macro for now.

But how exactly it manifests itself? Does it forbid Trait that has async methods to being used together with dyn, or users of code won'y see dyn methods when they got Trait object? Like this methods being ignored in vtable

The relevant issue is linked above in #107011

There is currently no compiler progress (the proposed feature flag doesn't even exist yet), and any attempt to use async_fn_in_trait in a trait object results in an error since the trait is not object safe:

error[[E0038]](https://doc.rust-lang.org/nightly/error_codes/E0038.html): the trait `Trait` cannot be made into an object
 --> src/main.rs:8:13
  |
8 |     let _:  &dyn Trait = todo!();
  |             ^^^^^^^^^^ `Trait` cannot be made into an object
  |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
 --> src/main.rs:4:14
  |
3 | trait Trait {
  |       ----- this trait cannot be made into an object...
4 |     async fn test(&self);
  |              ^^^^ ...because method `test` is `async`
  = help: consider moving `test` to another trait

Sorry if I am posting in a wrong thread. I have some confusion over using impl in trait function return type allowed by the return_position_impl_trait_in_trait feature, and async streams in the async_stream crate

Please see the following code segment:

#![feature(return_position_impl_trait_in_trait)]

use std::time::Duration;
use async_trait::async_trait;
use async_stream::stream;
use futures::{Stream, StreamExt, pin_mut};

// #[async_trait]
pub trait Iterator {
    fn iterate(&self) -> impl Stream<Item = usize> + '_;
}

pub struct Implementer {}

impl Implementer {
    fn generate(&self) -> impl Stream<Item = usize> {
        stream! {
            yield 1;
        }
    }
}

// #[async_trait]
impl Iterator for Implementer {
    fn iterate(&self) -> impl Stream<Item = usize> + '_ {
    
        // uncomment the next line to get an error [E0728]
        //tokio::time::sleep(Duration::from_millis(10)).await;

        stream! {
            let inner = self.generate();
            pin_mut!(inner);

            while let Some(i) = inner.next().await {
                yield i;
            }
        }
    }
}

If the awaited sleep is there, I get the error following error:

error[E0728]: `await` is only allowed inside `async` functions and blocks
  --> src\generation\iterator.rs:25:54
   |
24 | /     fn iterate(&self) -> impl Stream<Item = usize> + '_ {
25 | |         tokio::time::sleep(Duration::from_millis(10)).await;
   | |                                                      ^^^^^^ only allowed inside `async` functions and blocks
26 | |
27 | |         stream! {
...  |
34 | |         }
35 | |     }
   | |_____- this is not `async`

However an await is already there at the next() method call of the inner stream.

I am a little confused how this is supposed to work. If I try to add async to the trait method I get another error saying:

error[E0562]: `impl Trait` only allowed in function and inherent method return types, not in paths
  --> src\generation\iterator.rs:24:32
   |
24 |     async fn iterate(&self) -> impl Stream<Item = usize> + '_ {

Is there any known workaround for this situation? Thank you in advance.

Hi, this is off topic for this tracking issue. The compiler is correct that you cannot use await outside of async functions and blocks, and the stream! macro probably expands to an async block, which is why you're able to use it there.

Sorry for the off topic comment then. I appretiate your answer. Thank you

Is this still on track for 1.74, as mentioned by the blog post that had come out earlier?

Currently (on stable), anywhere you see an async fn1, you know that it does no "work"2 when called, and only does work when .awaited or .poll()ed3. If it is allowed to manually desugar async fn in a trait definition as fn() -> impl Future in a trait impl, then this becomes no longer true, as one can insert computation into the function defintion, e.g.

trait Trait {
  async fn foo();
}
impl Trait for () {
  fn foo -> impl Future<Output = ()> {
    println!("Doing stuff!");
    async {}
  }
}

I don't think this is a "blocker" but perhaps that section of the async book and the reference could be updated to reflect this change.

Footnotes

  1. outside of a macro โ†ฉ

  2. For some defintion of "work" including anything but copying parameters into the return value โ†ฉ

  3. This is my understanding at least, and it is somewhat supported by the async book and the Rust Reference: "The value returned by async fn is a Future. For anything to happen, the Future needs to be run on an executor." (emphasis mine) from the async book, and "Async functions do no work when called: instead, they capture their arguments into a future. When polled, that future will execute the function's body" (emphasis mine) from the Rust Reference โ†ฉ

For the record, a stabilization PR has been opened over at: #115822.

#91611 (comment): @zachs18: Yeah, I think the async book should be adjusted to mention something along the lines of "future isn't, in general, gonna to be run to completion unless it is awaited" -- the point, afaict, is more to make sure folks know .await needs to be called on the future unlike other langs which spawn tasks and kick them off immediately as a part of their async desugaring or something.