rfcs
rfcs copied to clipboard
Generic Futures
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 Waker
s 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.
Nice try! It looks like you miss sections "What is now impossible(or extremely complicated)" and "How new proposal solves this complications".
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).
@rustbot label A-futures
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.
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.
But to my knowledge, it is
fn foo() -> impl Trait
that desugars tofn foo() -> AnonymousType
whereAnonymousType
can't be named, and only exposes theTrait
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 theimpl Future
step of desugaring, immediately going to theAnonymousType
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.
But to my knowledge, it is
fn foo() -> impl Trait
that desugars tofn foo() -> AnonymousType
whereAnonymousType
can't be named, and only exposes theTrait
bound.In other words to be more accurate it desugars to
fn foo() -> AnonymousType where AnonymousType : Trait
. Thewhere
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 :)
Your rendered link is broken
Your rendered link is broken
Thanks for notifying me, it should be fixed now :)
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.
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.
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:
Ideally, it would desugar to
fn foo() -> impl for<_W: _WakerTrait> Future<_W, Output = ()>
, for this use offor
only works for lifetimes (unfortunately). This means RPIT is insufficient to expressasync 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?