Repository navigation
Open
Labels
A-async-awaitArea: Async & AwaitArea: Async & AwaitAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.Async-await issues that have been triaged during a working group meeting.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCCategory: An issue tracking the progress of sth. like the implementation of an RFCF-context_ext`#![feature(context_ext)]``#![feature(context_ext)]`T-libsRelevant to the library team, which will review and decide on the PR/issue.Relevant to the library team, which will review and decide on the PR/issue.WG-asyncWorking group: Async & awaitWorking group: Async & await
Description
Feature gate: #![feature(context_ext)]
This is a tracking issue for allowing std::task::Context to carry arbitrary extension data.
Public API
impl Context {
fn ext(&mut self) -> &mut dyn Any;
}
impl ContextBuilder {
fn ext(self, data: &'a mut dyn Any) -> Self;
fn from(cx: &'a mut Context<'_>) -> Self;
fn waker(self, waker: &'a Waker) -> Self;
}Steps / History
- Implementation: Add
Context::ext#123203 - Final comment period (FCP)1
- Stabilization PR
Unresolved Questions
- passing refs maybe unsound
- too complex
- This causes Context to no longer be UnwindSafe, which may be a breaking change: regression: the type
(dyn Any + 'static)may contain interior mutability #125193 (the issue is closed but there still is a FIXME in the code)
Footnotes
Reacted by Sergej Jaskiewicz, Sebastian Urban, anatawa12, teor, Vladislav Sukhmel and Pop
Metadata
Metadata
Assignees
Labels
A-async-awaitArea: Async & AwaitArea: Async & AwaitAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.Async-await issues that have been triaged during a working group meeting.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCCategory: An issue tracking the progress of sth. like the implementation of an RFCF-context_ext`#![feature(context_ext)]``#![feature(context_ext)]`T-libsRelevant to the library team, which will review and decide on the PR/issue.Relevant to the library team, which will review and decide on the PR/issue.WG-asyncWorking group: Async & awaitWorking group: Async & await
Activity
Rollup merge of rust-lang#125392 - workingjubilee:unwind-a-problem-in…
Unrolled build for rust-lang#125392
Rollup merge of #125392 - workingjubilee:unwind-a-problem-in-context,…
jkarneges commentedon Jun 8, 2024
One of the concerns raised was about the soundness of trying to force the
dyn Anyto carry non-static references via unsafe casts.I believe this may be theoretically achievable by requiring any references contained within the
dyn Anyto be of dyn types, which are!Sizedand thus prevent move-ins and move-outs of themselves, and for the trait definitions of those types to not expose any held lifetimes. Constraining inner references to such types could be done by introducing an unsafe trait:Since we are working with dyn references, we need the trait to be object-safe. This means we cannot allow casting to arbitrary lifetimes. Fortunately, we only ever need to cast to static or to the lifetime of self (which we here refer to as a "clamped" lifetime), and so we can provide methods for exactly those.
Implementing on a dyn type could look like this:
Above we have a non-static concrete type holding data with lifetime
'athat implements a trait which does not expose the'a. If all a user has is a&mut dyn StringRef, then there is no way to move adyn StringRefinto that address or move thedyn StringRefout of that address, and the available methods on the type (get()) only expose data with the lifetime of self. The lifetime of the concrete type is not exposed, and so it is safe to lie about it. Whether the lifetime is casted to'staticor some'foo, the user can't do anything with it.The
Any::downcast_mut()method is limited to casting to references of types that areSized, so we can't cast a dyn reference directly to&mut dyn Any. However, we can cast a dyn reference to a pointer, and then cast a reference to the pointer to the&mut dyn Any. To make this safe, we can wrap the pointer:With the above API, it becomes possible for the Context extension to carry non-static data:
This is a bit more complicated than I would have liked, but I can't think of a better way to do it, and I can't think of a better data structure than
Anyto allow carrying arbitrary data.jkarneges commentedon Jun 8, 2024
I should note that passing
'staticdata, whether single owner or with something likeRc, is of course possible and a lot simpler:Single owner:
With
Rc:I would guess many executors could reasonably use something like
Rc/Arcfor their extension data without runtime cost by pre-allocating such structures. In my experience, executors typically keep most of their state on the heap behind smart pointers anyway.Passing non-static references in extension data may be more interesting for combinators or nested executors, i.e. things that may want to avoid allocations and can't easily pre-allocate.
jkarneges commentedon Jun 15, 2024
I would like to move this towards stabilization.
The goal of this feature is to enable experimentation of
Contextextensions, that could later be promoted to direct fields/methods ofContext. Extensions that could not actually be promoted to direct fields, for example those requiring additional generics, are out of scope.Using this feature, I've successfully implemented my own extension,
TopLevelPollerin the waker-waiter crate, accessible by reference, which conceivably could be promoted to a&'a mut dyn TopLevelPollerfield ofContext.To respond to the unresolved questions:
I believe the approach I described earlier is sound. Would love feedback.
Specifically, @joshtriplett earlier wrote:
It's one field. Is this saying we should not allow
Contextto communicate more kinds of data?After trying to shoehorn non-static references into it, I can agree with this statement. It kind of is what it is though.
True and of course not what is being proposed.
It's an optional feature with no cost if not used, other than increasing the size of
Contextby the size of a fat pointer. Is this saying we should not add a field toContextunless it is needed by all async runtimes?I see a future where there are many extensions that executors and futures can opt-in to, that are best negotiated via the
Context, and not every app would use all possible extensions. I think this is a reasonable stance? Maybe we need some community consensus on the notion that it would be valuable forContextto be able to carry optional data.Unless there's more to it, I assume this a weighing between using thread-local/global storage vs slightly increasing the size of
Context. I suspect most people developing async apps would prefer the latter.Does anyone know how meaningful this is?
traviscross commentedon Jul 11, 2024
@rustbot labels +AsyncAwait-Triaged
We discussed this in the WG-async meeting today. We have enough reservations about this approach that we'd like to explore two main other approaches to solving this problem first.
One alternative is the
ProviderAPI, as in:error_generic_member_access#99301The idea there would be to broaden the scope of that producer/consumer API from just
Error, as it is now. We're interested in exploring how this could be done and unblocked.The other approach is making
Futuregeneric, as in:For this one, in particular, we'd need to do a lot of feasibility and migration analysis.
In general, we're interested in looking into and comparing these approaches.
We find the problem that the context extension is trying to solve highly motivating, but we want to be sure to find the right solution.
thomaseizinger commentedon Mar 22, 2025
I just discovered this feature and I absolutely love it. I've been toying around with co-routines as well to find a way how I can express sans-IO code such that the compiler takes care of composing the state machines for me the same way I can use
async/awaitcode.With this feature, it is possible to build a "runtime" and accompanying
Futures that are entirely sans-IO and still hook into the compiler's machinery ofasync/await. You can see the code here: https://github.com/thomaseizinger/sairunThis is super cool because it means we get the best of both worlds:
async/awaitinstead of our own state machinesIn regards to the proposed alternatives:
This is essentially a direct replacement so this alternative would also work for.
I've not thought about this too much but I think it would also be a suitable replacement.
danielhenrymantilla commentedon Oct 30, 2025
W.r.t. soundness and whatnot, @jkarneges, we can do something along the lines of the
ProviderAPI:It boils down to defining
dyn AnyLt<'x>, and then using&mut Option<impl Anylt<'a>> as &mut dyn AnyLt<'a>(the&mut Option<_>acting as a poorman'sdyn-safe&ownreference).But I don't think we need to really hide the
Optiontrick in your API; we'd rather "just" expose&and&mutgetters, and users would be the ones to think of using anOptionaround theirowned_datato retrieve it in an owned fashion (see below).A sound
dyn Any<'lt>is actually "easy" to design soundly if you use a one-to-one mapping from a lifetime-infected type to its'static-TypeId-yielding "body", and exactly that one'ltlifetime.That is, you can even define it as a non-
unsafetrait exposed to arbitrary implementations by having a doubly-injective (hence bijective) definition for this one-to-one restriction, so that the trait solver itself prevent unsound implementations:Click to see
Once you have this, you can define the
Any<'lt>trait and sound downcast API:Click to see
And once you have such
dyn Any<'lt>, you can expose the alloc-free setters and getters:Demo:
Playground
jkarneges commentedon Nov 8, 2025
@danielhenrymantilla amazing!
There is also Provider-style API for Context as an alternative proposal to address concerns raised about
context_ext. However, if your approach is sound then it may be simpler.At this point I think pretty much all the ways Context can be extended have been proposed and we need to pick one.
FeldrinH commentedon Dec 18, 2025
I want to point out one use case where keeping the size of Context small is valuable and these extensions are rarely needed: async executors on embeddeded targets.
On embedded targets the cost of these fat pointers can add up, especially if a new Context is created for every call to poll (this is done in every embedded async executor I've looked at). A few extra pointers in Context probably won't hurt performance much, but having "many extensions that executors and futures can opt-in to" sounds like it might.
George-Miao commentedon Mar 25, 2026
Hi! Maintainer of compio here. We have been experimenting with
Context::extlately to implement our future combinators: https://doc.compio.rs/x86_64-unknown-linux-gnu/compio/runtime/trait.FutureExt. We useextto carry and pass down additional data and eventually get consumed by runtime to alter behaviors. Some feedbacks: