-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
[Stabilization] Future APIs #59725
Comments
@rfcbot fcp merge |
Team member @withoutboats has proposed to merge this. The next step is review by the rest of the tagged team members:
Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Another thing to note is that we need a Basically we need a way on stable Rust to pass values of This has not been implemented yet but will be done so adjacent to merging the stabilization PR (hopefully before, and ideally it should not need beta backporting since we have some days to get it merged before master=>beta promotion). @oli-obk @cramertj Can one of you do the impl / review? (also cc me on that PR) The change to add @rfcbot reviewed |
Another thing probably worth calling out is the list of There's an impl for These can be found here. |
It just crossed my mind that the Does anyone know if that had been discussed before? Or even tried and found "subtly impossible (atm)"? |
So... based on @eddyb's idea and discussions with them as well as with @Nemo157, extern {
type Private;
}
pub struct RawWaker {
data: NonNull<Private>,
vtable: &'static RawWakerVTable<Private>,
}
pub struct RawWakerVTable<T: ?Sized = ()> {
clone: for<'a> fn(&'a T) -> RawWaker,
wake: unsafe fn(NonNull<T>),
wake_by_ref: for<'a> fn(&'a T),
drop: unsafe fn(NonNull<T>),
}
impl RawWaker {
pub fn new<T>(data: NonNull<T>, vtable: &'static RawWakerVTable<T>) -> Self {
...
}
}
impl<T> RawWakerVTable<T> {
#[rustc_promotable]
#[rustc_allow_const_fn_ptr]
pub const fn new(
clone: for<'a> fn(&'a T) -> RawWaker,
wake: unsafe fn(NonNull<T>),
wake_by_ref: for<'a> fn(&'a T),
drop: unsafe fn(NonNull<T>),
) -> Self {
...
}
} No other public facing changes are made. An implementation exists in a playground (aside from The rationale for doing so is as @eddyb put it:
In particular, we move from |
Note that if you just change |
FWIW, w/ raw pointers, it is possible to store additional data in used pointer bits, rendering the pointer invalid. This would be fine if the invalid pointer is passed to a vtable fn as a raw pointer, but passing it as a ref would forbid this use case. |
@carllerche So then to support that use case you'd want?: pub struct RawWakerVTable<T: ?Sized = ()> {
clone: unsafe fn(NonNull<T>) -> RawWaker,
wake: unsafe fn(NonNull<T>),
wake_by_ref: unsafe fn(NonNull<T>),
drop: unsafe fn(NonNull<T>),
} ( The change to |
@Centril I don't have an opinion on the details as long as the use cases are supported (non valid pointers and forwards compatibility). |
@carllerche Fair :) Thanks for the input! |
Why |
@Matthias247 Well the thinking was that you wouldn't want to pass in Really, the bigger point was to move from |
@eddyb @Centril I am not in favor of pursuing this change to the futures API. First, the only additional check you're encoding in the type system is that when you construct a RawWaker the data pointer you pass has the same type as the arguments to your vtable function. This will not actually prevent you from having to do unsafe casts, since you want is not actually a *const but actually usually an Arc (or in niche use cases not an arc, but also not a raw pointer). This type is also likely to be arbitrary, for that reason, rather than a real meaningful type - I would probably just keep using Also, this is a niche use case (creating a waker) and even in that niche use case there is a much more ergonomic and type safe API we eventually want to add for the majority of those use cases, which would look roughly like this: trait Wake {
fn wake(self: Arc<Self>);
// default impl overrideable if you dont need ownership
fn wake_by_ref(self: &Arc<Self>) {
self.clone().wake()
}
}
impl Waker {
pub fn new(wake: Arc<dyn Wake>) -> Waker { ... }
} This is blocked on a few other things (&Arc as a valid receiver, changes to the std/core infrastructure so we can add std-only methods to core types), so we decided to stick with the RawWaker API for the moment. But in the long term, RawWaker will be a niche within a niche: constructing wakers that don't conform to the simple majoritarian implementation of just being an arc'd trait object. These are things like embedded executors and the intermediate executors in concurrency primitives like futures-unordered. So this seems like it would complicate the API without actually benefiting the targeted users, who are also a small minority of users. The |
It won't, this is true. However, if we take e.g. @Nemo157's embrio-rs then we can move from: // Leaving out `wake_by_ref` here... it would ofc be included in a real implementation.
static EMBRIO_WAKER_RAW_WAKER_VTABLE: RawWakerVTable = RawWakerVTable {
clone: |data| unsafe { (*(data as *const EmbrioWaker)).raw_waker() },
wake: |data| unsafe { (*(data as *const EmbrioWaker)).wake() } },
drop,
};
pub(crate) fn raw_waker(&'static self) -> RawWaker {
RawWaker::new(
self as *const _ as *const (),
&EMBRIO_WAKER_RAW_WAKER_VTABLE,
)
} into: static EMBRIO_WAKER_RAW_WAKER_VTABLE: RawWakerVTable<EmbrioWaker> = RawWakerVTable {
clone: |data| unsafe { (*data).raw_waker() },
wake: |data| unsafe { (*data).wake() } },
drop,
};
pub(crate) fn raw_waker(&'static self) -> RawWaker {
RawWaker::new(
self as *const _,
&EMBRIO_WAKER_RAW_WAKER_VTABLE,
)
} This seems to me strictly better, ergonomic, and readable since it has fewer casts.
The use of
As the generic parameter (To be clear, the version I'm talking about here is: pub struct RawWakerVTable<T: ?Sized = ()> {
clone: unsafe fn(*const T) -> RawWaker,
wake: unsafe fn(*const T),
wake_by_ref: unsafe fn(*const T),
drop: unsafe fn(*const T),
} )
I understand this point but does that matter? Even within the niche of the niche it provides an improvement without any cost to those that only ever want |
In most cases isn't the pointer being passed around actually the |
@rfcbot concern RawWakerVTable-if-generic-would-be-better-even-if-only-for-niche-uses |
If the pointer is being passed around that way (in an The common case will already be addressed primarily through entirely safe traits. Adding extra guardrails to the unsafe escape hatch isn't necessary here. |
I am ambivalent here. I see the benefits of making EDIT: To be clear, for this reason, I'd probably be inclined to move forward with the API as it is, so as to avoid further delay. |
@cramertj my goal was to register a blocking issue. I did not mean for the FCP timer to be reset (that is, I did not realize that would be a consequence of filing such a concern), but I did want to ensure that the future API's would not be stabilized with that issue outstanding. |
Yeah, I'm not sure we have a lever for that unfortunately. It seems like what you'd want is to issue a blocking concern on the stabilization PR so that that PR can only be merged once the issue is fixed, but I don't think we have a mechanism for that at the moment other than leaving a comment. |
Will the timer resume after the concern is marked as resolved, or will it reset to 10 days? |
Yep. Remember that rfcbot is a tool. We don't have to always follow the bot to the letter. |
#60088 has landed. |
It has now been 10 days since the final comment period started. |
@rfcbot resolve async-fn-should-not-be-allowed |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
@rfcbot another ten days? |
nah, now we get to ignore rfcbot ;) |
Stabilize futures_api cc rust-lang#59725. Based on rust-lang#59733 and rust-lang#59119 -- only the last two commits here are relevant. r? @withoutboats , @oli-obk for the introduction of `rustc_allow_const_fn_ptr`.
Stabilize futures_api cc rust-lang#59725. Based on rust-lang#59733 and rust-lang#59119 -- only the last two commits here are relevant. r? @withoutboats , @oli-obk for the introduction of `rustc_allow_const_fn_ptr`.
Stabilize futures_api cc rust-lang#59725. Based on rust-lang#59733 and rust-lang#59119 -- only the last two commits here are relevant. r? @withoutboats , @oli-obk for the introduction of `rustc_allow_const_fn_ptr`.
Stabilize futures_api cc rust-lang#59725. Based on rust-lang#59733 and rust-lang#59119 -- only the last two commits here are relevant. r? @withoutboats , @oli-obk for the introduction of `rustc_allow_const_fn_ptr`.
Stabilization PR was merged. |
@Centril How far is 1.36.0 from roll-out? |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. The RFC will be merged soon. |
Feature name: futures_api
Stabilization target: 1.36.0
Tracking issue: #59113
Related RFCs:
std::task
andstd::future::Future
rfcs#2592 Futures APII propose that we stabilize the
futures_api
feature, making the Future trait available on stable Rust. This is an important step in stabilizing the async/await feature and providing a stable, ergonomic, zero-cost abstraction for async IO in Rust.The futures API was first introduced as a part of the std library prior to 1.0. It was removed from std shortly after 1.0, and was developed outside of std in an external crate called futures, first released in 2016. Since that time, the API has undergone significant evolution.
All the APIs being stabilized are exposed through both core and std.
The
future
ModuleWe shall stabilize these items in the future module:
std::future
module itselfstd::future::Future
trait and both of its associated items (Output
andpoll
)We do not stabilize the other items in this module, which are implementation details of async/await as it currently exists that are not intended to be stabilized.
The
task
ModuleWe shall stabilize these items in the task module:
std::task
module itselfstd::task::Poll
enumstd::task::Waker
struct and all three of its methods (wake, wake_by_ref, will_wake, and new_unchecked). new_unchecked shall be renamed to from_raw.std::task::RawWaker
type and its methodnew
std::task::RawWakerVTable
type and its methodnew
(see Unexpected panic when going from debug to release build #59919)std::task::Context
type and its methodsfrom_waker
andwaker
Notice: Late Changes to the API
We have decided to merge the future-proofing changes proposed in #59119 to leave room for some potential extensions to the API after stabilization. See further discussion on that issue.
Notes on Futures
The poll-based model
Unlike other languages, the Future API in Rust uses a poll based execution model. This follows a back and forth cycle involving an executor (which is responsible for executing futures) and a reactor (which is responsible for managing IO events):
Eventually, the future returns ready instead of pending, indicating that the future has completed.
Pinning
The Future trait takes self by
Pin<&mut Self>
. This is based on the pinning APIs stabilized in 1.33. This contract allows implementers to assume that once a future is being polled, it will not be moved again. The primary benefit of this is that async items can have borrows across await points, desugared into self-referential fields of the anonymous future type.Changes proposed in this stabilization report
Waker::new_unchecked
is renamed toWaker::from_raw
std has unsafe constructors following both names, from_raw is more specific than new_unchecked (its a constructor taking the "raw" type which is possibly invalid, asserting that this instance is valid).
Waker::wake
takes self by value and newWaker::wake_by_ref
takes self by referenceThe most common waker implementation is to be an arc of the task which re-enqueues itself when the waker is woken; to implement this by reference, you must clone the arc and put it on the queue. But most uses of wake drop the waker as soon as they call wake. This results in an unnecessary atomic reference increment and decrement; instead we now provide by a by-value and by-reference implementation, so users can use the form most optimal for their situation.
Moderation note
The futures APIs have been discussed at enormous length over the past 3 years. Every aspect of the API has been debated, reviewed and considered by the relevant teams and the Rust community as a whole. When posting to this thread, please make a good faith effort to review the history and see if your concern or proposal has been posted before, and how and why it was resolved.
The text was updated successfully, but these errors were encountered: