-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Context::ext
#123203
Add Context::ext
#123203
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @Amanieu (or someone else) some time within the next two weeks. Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@rust-lang/wg-async Do you have any thoughts on this API? |
How does this compare with an API like the one for |
Yes, I think there will be a need for multiple extensions, but probably as traits and not just as concrete types, so I'm not sure if an API like Another thing I believe will be desirable is overriding certain extensions in sub-contexts. For example, it may be useful to offer an extension for spawning tasks that could initially be provided by the top level executor but overridden to create task arenas. Maybe that could work with a provider-like API by chaining the provide calls up through the parent contexts, and the first to return something wins. My main concern with a decision about this though is all experimentation would need to be against nightly. I would love to see the single |
With
Yeah I think this could work nicely with a
I'm not sure stable is the right place to do this kind of experimentation since if it turns out to be the wrong abstraction this is going to stick. Also, writing a provider-like on top of it is going to require |
This is what I did here https://docs.rs/context-rs/latest/src/context_rs/waker.rs.html#9-12. Two issues I see:
|
By "stick" do you mean end up mostly unused? If I suppose it would be unfortunate not only for introducing a field that may go unused, but also because it would imply that experimentation with the ideal data structure would need to happen on nightly. That is a pretty rough fate. If my example isn't sound, maybe someone else can come up with an approach that is. It's worth investigating very deeply into whether a simple field like |
Not objecting to experimenting with this, but registering that if this were up for stabilization I would currently expect to object to stabilizing it. I wanted to document this to make it clear that it's not as simple as "this solves the problem it set out to solve so let's stabilize it as-is". Concretely:
To be clear: still very much in favor of async interoperability, but I don't think this is a necessary component of async interoperability. Request: please link to this comment as an unresolved question in the tracking issue for this. |
How so? It's just an optional pointer-sized field. If nothing opts-in to use it, then it's basically zero cost.
Thread-local storage? Pass the data to the futures directly? These are not great options. There's also waker hijacking via waker_getters. It's not stabilized yet and seems like a worse solution for carrying extra data than a |
We discussed this in the libs-api meeting. We're OK with merging this to enable experimentation on nightly, but nobody was very happy with the API itself. We should only stabilize this if we have extensively explored other API solutions (such as rust-lang/libs-team#347, but that has its own drawbacks in terms of API complexity). r=me once a tracking issue has been created |
@Amanieu made a tracking issue |
@bors r+ |
Rollup of 5 pull requests Successful merges: - rust-lang#122865 (Split hir ty lowerer's error reporting code in check functions to mod errors.) - rust-lang#122935 (rename ptr::from_exposed_addr -> ptr::with_exposed_provenance) - rust-lang#123182 (Avoid expanding to unstable internal method) - rust-lang#123203 (Add `Context::ext`) - rust-lang#123380 (Improve bootstrap comments) r? `@ghost` `@rustbot` modify labels: rollup
Rollup merge of rust-lang#123203 - jkarneges:context-ext, r=Amanieu Add `Context::ext` This change enables `Context` to carry arbitrary extension data via a single `&mut dyn Any` field. ```rust #![feature(context_ext)] 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; } ``` Basic usage: ```rust struct MyExtensionData { executor_name: String, } let mut ext = MyExtensionData { executor_name: "foo".to_string(), }; let mut cx = ContextBuilder::from_waker(&waker).ext(&mut ext).build(); if let Some(ext) = cx.ext().downcast_mut::<MyExtensionData>() { println!("{}", ext.executor_name); } ``` Currently, `Context` only carries a `Waker`, but there is interest in having it carry other kinds of data. Examples include [LocalWaker](rust-lang#118959), [a reactor interface](rust-lang/libs-team#347), and [multiple arbitrary values by type](https://docs.rs/context-rs/latest/context_rs/). There is also a general practice in the ecosystem of sharing data between executors and futures via thread-locals or globals that would arguably be better shared via `Context`, if it were possible. The `ext` field would provide a low friction (to stabilization) solution to enable experimentation. It would enable experimenting with what kinds of data we want to carry as well as with what data structures we may want to use to carry such data. Dedicated fields for specific kinds of data could still be added directly on `Context` when we have sufficient experience or understanding about the problem they are solving, such as with `LocalWaker`. The `ext` field would be for data for which we don't have such experience or understanding, and that could be graduated to dedicated fields once proven. Both the provider and consumer of the extension data must be aware of the concrete type behind the `Any`. This means it is not possible for the field to carry an abstract interface. However, the field can carry a concrete type which in turn carries an interface. There are different ways one can imagine an interface-carrying concrete type to work, hence the benefit of being able to experiment with such data structures. ## Passing interfaces Interfaces can be placed in a concrete type, such as a struct, and then that type can be casted to `Any`. However, one gotcha is `Any` cannot contain non-static references. This means one cannot simply do: ```rust struct Extensions<'a> { interface1: &'a mut dyn Trait1, interface2: &'a mut dyn Trait2, } let mut ext = Extensions { interface1: &mut impl1, interface2: &mut impl2, }; let ext: &mut dyn Any = &mut ext; ``` To work around this without boxing, unsafe code can be used to create a safe projection using accessors. For example: ```rust pub struct Extensions { interface1: *mut dyn Trait1, interface2: *mut dyn Trait2, } impl Extensions { pub fn new<'a>( interface1: &'a mut (dyn Trait1 + 'static), interface2: &'a mut (dyn Trait2 + 'static), scratch: &'a mut MaybeUninit<Self>, ) -> &'a mut Self { scratch.write(Self { interface1, interface2, }) } pub fn interface1(&mut self) -> &mut dyn Trait1 { unsafe { self.interface1.as_mut().unwrap() } } pub fn interface2(&mut self) -> &mut dyn Trait2 { unsafe { self.interface2.as_mut().unwrap() } } } let mut scratch = MaybeUninit::uninit(); let ext: &mut Extensions = Extensions::new(&mut impl1, &mut impl2, &mut scratch); // ext can now be casted to `&mut dyn Any` and back, and used safely let ext: &mut dyn Any = ext; ``` ## Context inheritance Sometimes when futures poll other futures they want to provide their own `Waker` which requires creating their own `Context`. Unfortunately, polling sub-futures with a fresh `Context` means any properties on the original `Context` won't get propagated along to the sub-futures. To help with this, some additional methods are added to `ContextBuilder`. Here's how to derive a new `Context` from another, overriding only the `Waker`: ```rust let mut cx = ContextBuilder::from(parent_cx).waker(&new_waker).build(); ```
This change enables
Context
to carry arbitrary extension data via a single&mut dyn Any
field.Basic usage:
Currently,
Context
only carries aWaker
, but there is interest in having it carry other kinds of data. Examples include LocalWaker, a reactor interface, and multiple arbitrary values by type. There is also a general practice in the ecosystem of sharing data between executors and futures via thread-locals or globals that would arguably be better shared viaContext
, if it were possible.The
ext
field would provide a low friction (to stabilization) solution to enable experimentation. It would enable experimenting with what kinds of data we want to carry as well as with what data structures we may want to use to carry such data.Dedicated fields for specific kinds of data could still be added directly on
Context
when we have sufficient experience or understanding about the problem they are solving, such as withLocalWaker
. Theext
field would be for data for which we don't have such experience or understanding, and that could be graduated to dedicated fields once proven.Both the provider and consumer of the extension data must be aware of the concrete type behind the
Any
. This means it is not possible for the field to carry an abstract interface. However, the field can carry a concrete type which in turn carries an interface. There are different ways one can imagine an interface-carrying concrete type to work, hence the benefit of being able to experiment with such data structures.Passing interfaces
Interfaces can be placed in a concrete type, such as a struct, and then that type can be casted to
Any
. However, one gotcha isAny
cannot contain non-static references. This means one cannot simply do:To work around this without boxing, unsafe code can be used to create a safe projection using accessors. For example:
Context inheritance
Sometimes when futures poll other futures they want to provide their own
Waker
which requires creating their ownContext
. Unfortunately, polling sub-futures with a freshContext
means any properties on the originalContext
won't get propagated along to the sub-futures. To help with this, some additional methods are added toContextBuilder
.Here's how to derive a new
Context
from another, overriding only theWaker
: