rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Generic Futures

Open p-avital opened this issue 1 year ago • 13 comments

This RFC proposes adding a (defaulted) generic parameter to the core::future::Future trait to allow more flexibility in Future::poll's second argument (context).

This is would allow new Wakers to be created, that may be used to:

  • Allow the creation of ABI-stable wakers, allowing futures constructed by a shared object to be run on another's executor.
  • Allow wakers to adopt other structures that that of vtable/pointer pairs, which may be desirable to certain domains.
  • Allow specific futures to rely on additional behaviour of specific wakers that certain executors may provide.

Rendered

p-avital avatar May 22 '23 08:05 p-avital

Nice try! It looks like you miss sections "What is now impossible(or extremely complicated)" and "How new proposal solves this complications".

VitWW avatar May 22 '23 15:05 VitWW

Nice try! It looks like you miss sections "What is now impossible(or extremely complicated)" and "How new proposal solves this complications".

Cheeky, I actually went and checked the template again x)

For reference, here's a RFC I previous opened on trying to make the current Waker ABI stable without causing breaks in the std library. Unfortunately, doing so would impose a runtime cost on every program that uses futures, regardless of whether or not they care about ABI stability, which doomed said RFC.

With this proposal, new wakers can be created to fit any design constraint we please, provided they have interior mutability (and by adding a waker_mut accessor to Context, even that restriction could be lifted).

p-avital avatar May 22 '23 15:05 p-avital

@rustbot label A-futures

Jules-Bertholet avatar May 25 '23 02:05 Jules-Bertholet

The desugaring of async functions/blocks seems under-spec'd to me. Like, if async fn foo() desugars to fn foo<_W>() -> impl Future<_W, Output = ()> where SomeSubFuture: Future<_W>, then any async block depending on foo would have to have some sort of way to specify where foo<_W>; that the generic matches the bounds in another function's where clause.

coolreader18 avatar May 25 '23 07:05 coolreader18

Indeed, this makes desugaring to RPIT impossible without adding a generic argument to the funciton.

Ideally, it would desugar to fn foo() -> impl for<_W: _WakerTrait> Future<_W, Output = ()>, for this use of for only works for lifetimes (unfortunately). This means RPIT is insufficient to express async fn.

But to my knowledge, it is fn foo() -> impl Trait that desugars to fn foo() -> AnonymousType where AnonymousType can't be named, and only exposes the Trait bound.

So I think async fn foo() should straight up skip the impl Future step of desugaring, immediately going to the AnonymousType representation.

p-avital avatar May 25 '23 07:05 p-avital

But to my knowledge, it is fn foo() -> impl Trait that desugars to fn foo() -> AnonymousType where AnonymousType can't be named, and only exposes the Trait bound.

In other words to be more accurate it desugars to fn foo() -> AnonymousType where AnonymousType : Trait. The where is importnat.

So I think async fn foo() should straight up skip the impl Future step of desugaring, immediately going to the AnonymousType representation.

And you still have the same problem, because it is the where for<_W: WakerTrait> AnonymousType : Future<_W, Output=()> that you still can't write.

… and if the poll method was generic instead, the trait would stop being object safe, so it would stop working in a Box and that is also essential.

jan-hudec avatar Jun 01 '23 17:06 jan-hudec

But to my knowledge, it is fn foo() -> impl Trait that desugars to fn foo() -> AnonymousType where AnonymousType can't be named, and only exposes the Trait bound.

In other words to be more accurate it desugars to fn foo() -> AnonymousType where AnonymousType : Trait. The where is importnat.

On the contrary, the where clause isn't important, because AnonymousType does implement Future<_W> for the open set of _W with the appropriate bounds. Anonymous types tend to put us in "we have to tell the function what the type implements" mode, but you've never had to write fn foo() -> String where String: Display, have you? To the compiler, AnonymousType is no different from String, it's just that we can't name it.

I've updated the RFC to make these anonymous type shenanigans more readable. Thanks for the input :)

p-avital avatar Jun 04 '23 06:06 p-avital

Your rendered link is broken

Fishrock123 avatar Jun 08 '23 18:06 Fishrock123

Your rendered link is broken

Thanks for notifying me, it should be fixed now :)

p-avital avatar Jun 08 '23 19:06 p-avital

The core::task::Waker type is the common denominator to all interactions with futures, making adjusting its API and implementation especially trying, as it affects the whole ecosystem indiscriminately

I'd think that changes to wakers and futures would consider a way to leave some room for completion-based (as opposed to readiness-based) asynchrony.

Maybe that's outside of the scope here. Maybe not.

e-dant avatar Jul 29 '23 18:07 e-dant

We should probably nominate this for the @rust-lang/wg-async triage. I don't think we have a label for that here, but tagging us so we can track it.

yoshuawuyts avatar Feb 14 '24 12:02 yoshuawuyts

Thanks @yoshuawuyts

If anyone knowledgeable is reading this, I'm looking for some help in prototyping this RFC. I'm not familiar with the rustc's codebase, and it looks nothing like proc-macros which is about where my knowledge of code-generation in Rust ends, so any guidance would be appreciated :sweat_smile:

I'm looking at modifying StateTransform::run_pass, or maybe transform_async_context, or maybe I'm looking completely at the wrong place... Please send help :sweat_smile:

p-avital avatar Feb 14 '24 14:02 p-avital

Ideally, it would desugar to fn foo() -> impl for<_W: _WakerTrait> Future<_W, Output = ()>, for this use of for only works for lifetimes (unfortunately). This means RPIT is insufficient to express async fn.

That desugaring cannot be made into a concrete type, so most likely this: fn foo<_W: _WakerTrait = std::task::waker>() -> impl Future<_W, Output = ()> would work.

and if so, potential executor trait can be given an assoc type for precisely its context, then we can have

trait Future<E: std::task::Executor> { 
   type Output;
   fn poll(self: Pin<&mut self>, cx: E::Ctx) -> Poll<Self::Output>; 
}

In the air: adapter between executors so that it's possible to have more than one?

tema3210 avatar Jul 12 '24 17:07 tema3210