From 7d364aa189c1db7c801846a236294e24f58e49f4 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Tue, 24 Apr 2018 16:37:03 -0700 Subject: [PATCH 01/22] RFC: add Async trait and task system to libcore --- text/0000-async.md | 774 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 774 insertions(+) create mode 100644 text/0000-async.md diff --git a/text/0000-async.md b/text/0000-async.md new file mode 100644 index 00000000000..0687d969da6 --- /dev/null +++ b/text/0000-async.md @@ -0,0 +1,774 @@ +- Feature Name: (fill me in with a unique ident, my_awesome_feature) +- Start Date: 2018-04-04 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +This RFC provides the library component for the first-class `async`/`await` +syntax proposed in a [companion RFC]. It is intentionally minimal, including the +smallest set of mechanisms needed to support async/await with borrowing and +interoperation with the futures crate. Those mechanisms are: + +- The task system of the futures crate, which will be moved into `libcore` +- A new `Async` trait, which integrates the [`Pin` APIs][pin] with the task system to + provide async values that can interoperate with futures. + +[pin]: https://github.com/rust-lang/rfcs/pull/2349 +[companion RFC]: https://github.com/rust-lang/rfcs/pull/2394 + +The RFC also covers the intended ecosystem migration path, as well as the +possibility of eventually deprecating `Future` in favor of `Async`. + +# Motivation +[motivation]: #motivation + +The basic motivation for this RFC is to provide a supporting mechanism for +`async`/`await` syntax: + +```rust +async fn function(argument: &str) -> usize { + // ... +} +``` + +The syntax itself is motivated in the [companion RFC], and there is +a [blog post](http://aturon.github.io/2018/04/24/async-borrowing/) that goes +through its importance in greater detail. As with closures, the syntax involves +producing an anonymous type, so that the above declaration is equivalent to: + +```rust +fn function<'a>(argument: &'a str) -> _Anonymous<'a, usize> { + // ... +} +``` + +Again like a closure the anonymous type is only usable through the trait it +implements: `Async`. The goal of this RFC is to provide a concrete proposal for +this `Async` trait, based on the work pioneered by the futures crate. + +A secondary benefit of this RFC is that it enshrines the *task system* currently +defined by the futures crate into `libcore`, thereby standardizing and +ultimately stabilizing the async ecosystem around a single lightweight task +mechanism. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +The `Async` trait represents an *asynchronous* and lazy computation that may +eventually produce a final value, but doesn't have to block the current thread +to do so. + +Async values can be constructed through `async` blocks or `async` functions, e.g., + +```rust +async fn read_frame(socket: &TcpStream) -> Result { ... } +``` + +This `async` function, when invoked, produces an async value that represents the +completion of reading a frame from the given socket. The function signature +is equivalent to: + +```rust +fn read_frame<'sock>(socket: &'sock TcpStream) + -> impl Async> + 'sock; +``` + +Other async functions can *await* this asynchronous value; see the [companion +RFC] for full details. + +In addition to `async fn` definitions, async values can be built using adapters on +the `Async` trait, much like with `Iterator`s. The standard library includes a +number of basic adapters (described in the reference below), while some +particularly interesting variants are iterating in the crates.io ecosystem +first. + +Ultimately asynchronous computations are executed by *tasks*, which are +lightweight threads. In particular, an *executor* is able to "spawn" a +`()`-producing `Async` value as an independent task; these tasks are then +cooperatively scheduled onto one or more operating system threads. The +`Executor` trait defines this interface, and the `task` module provides a host +of related definitions needed when manually implementing `Async` values or +executors. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## `core::task` module + +The fundamental mechanism for asynchronous computation in Rust is *tasks*, which +are lightweight threads of execution; many tasks can be cooperatively scheduled +onto a single operating system thread. + +To perform this cooperative scheduling we use a technique sometimes referred to +as a "trampoline". When a task would otherwise need to block waiting for some +event, instead it schedules itself for later wakeup and *returns* to the +executor running it, which can then run another task. Subsequent wakeups place +the task back on the executors queue of ready tasks, much like a thread +scheduler in an operating system. + +Attempting to complete a task (or async value within it) is called *polling*, +and always yields a `Poll` value back: + +```rust +/// Indicates whether a value is available, or if the current task has been +/// scheduled for later wake-up instead. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Poll { + /// Represents that a value is immediately ready. + Ready(T), + + /// Represents that a value is not ready yet. + /// + /// When a function returns `Pending`, the function *must* also + /// ensure that the current task is scheduled to be awoken when + /// progress can be made. + Pending, +} +``` + +When a task returns `Poll::Ready`, the executor knows the task has completed and +can be dropped. + +### Waking up + +Each task executor provides its own scheduling facilities, and hence needs to +customize the way task wakeups are handled. As such, there is a +`std::task::Wake` trait defining wakeup behavior: + +```rust +/// A way of waking up a specific task. +/// +/// Any task executor must provide a way of signaling that a task it owns +/// is ready to be `poll`ed again. Executors do so by providing a wakeup handle +/// type that implements this trait. +pub trait Wake: Send + Sync { + /// Indicates that the associated task is ready to make progress and should + /// be `poll`ed. + /// + /// Executors generally maintain a queue of "ready" tasks; `wake` should place + /// the associated task onto this queue. + fn wake(&self); +} +``` + +In general async values are not coupled to any particular executor, so we use a trait +object to handle waking: + +```rust +/// A `Waker` is a handle for waking up a task by notifying its executor that it +/// is ready to be run. +/// +/// This handle contains a trait object pointing to an instance of the `UnsafeWake` +/// trait, allowing notifications to get routed through it. +/// +/// Implements `Clone`, `Send`, and `Sync`. +pub struct Waker { ... } + +impl Waker { + /// Wake up the task associated with this `Waker`. + pub fn wake(&self); +} + +// We will see how to handle the no_std case later in the RFC... +impl From for Waker where Arc: Wake + 'static { ... } +``` + +Task execution always happens in the context of a `Waker` that can be used to +wake the task up; we'll see the full `core::task::Context` structure below. + +### Executors + +An executor is responsible for polling tasks to completion. We represent this +with the `core::task::Executor` trait: + +```rust +/// A task executor. +/// +/// A *task* is a `()`-producing async value that runs at the top level, and will +/// be `poll`ed until completion. It's also the unit at which wake-up +/// notifications occur. Executors, such as thread pools, allow tasks to be +/// spawned and are responsible for putting tasks onto ready queues when +/// they are woken up, and polling them when they are ready. +pub trait Executor { + /// Spawn the given task, polling it until completion. + /// + /// # Errors + /// + /// The executor may be unable to spawn tasks, either because it has + /// been shut down or is resource-constrained. + fn spawn_obj(&mut self, task: TaskObj) -> Result<(), SpawnObjError>; + + /// Determine whether the executor is able to spawn new tasks. + /// + /// # Returns + /// + /// An `Ok` return means the executor is *likely* (but not guaranteed) + /// to accept a subsequent spawn attempt. Likewise, an `Err` return + /// means that `spawn` is likely, but not guaranteed, to yield an error. + fn status(&self) -> Result<(), SpawnErrorKind> { + Ok(()) + } +} + +pub struct TaskObj { .. } + +impl TaskObj { + /// Create a new `TaskObj` by boxing the given future. + pub fn new + Send + 'static>(f: A) -> TaskObj; +} + +/// Provides the reason that an executor was unable to spawn. +pub struct SpawnErrorKind { .. } + +impl SpawnErrorKind { + /// Spawning is failing because the executor has been shut down. + pub fn shutdown() -> SpawnErrorKind; + + /// Check whether this error is the `shutdown` error. + pub fn is_shutdown(&self) -> bool; + + // additional error variants added over time... +} + +/// The result of a failed spawn +pub struct SpawnObjError { + /// The kind of error + pub kind: SpawnErrorKind, + + /// The task for which spawning was attempted + pub task: TaskObj, +} +``` + +We need the executor trait to be usable as a trait object, which is why `TaskObj` +is constructed here from a boxed future. (In the no_std section, we'll see +another constructor). In the long run, though, once we can take `dyn` by value, +we would deprecate `spawn_obj` and add a default `spawn` method: + +```rust +trait Executor { + fn spawn(&mut self, task: Async + Send) -> Result<(), SpawnErrorKind> { + self.spawn_obj(TaskObj::new(task)) + } + // ... +} +``` + +At that point we would also deprecate `TaskObj`, which is the reason for using +the `Obj` suffix -- we want to keep the name `Task` available for potential +usage down the line. + +In addition to the above, the `core::task` module will include the following API +for helping detect bugs: + +```rust +/// Marks the current thread as being within the dynamic extent of an +/// executor. +/// +/// Executor implementations should call this function before beginning to +/// execute a tasks, and drop the returned `Enter` value after completing +/// task execution: +/// +/// ```rust +/// let enter = enter().expect("..."); +/// /* run task */ +/// drop(enter); +/// ``` +/// +/// Doing so ensures that executors aren't accidentally invoked in a nested fashion. +/// When that happens, the inner executor can block waiting for an event that can +/// only be triggered by the outer executor, leading to a deadlock. +/// +/// # Error +/// +/// Returns an error if the current thread is already marked, in which case the +/// caller should panic with a tailored error message. +pub fn enter() -> Result +``` + +As stated in the doc comment, the expectation is that all executors will wrap +their task execution within an `enter` to detect inadvertent nesting. + +### Task contexts + +All tasks are executed with two pieces of contextual information: + +- A `Waker` for waking the task up later on. +- An executor, which is the "default" place to spawn further tasks. + +Notably, this list does *not* include task-local data; that can be addressed +externally, as we'll see in a later section. + +The `core::task::Context` type gathers the (stack-rooted) contextual information +together, and is passed by mutable reference to all polling functions: + +```rust +/// Information about the currently-running task. +/// +/// Contexts are always tied to the stack, since they are set up specifically +/// when performing a single `poll` step on a task. +pub struct Context<'a> { .. } + +impl<'a> Context<'a> { + pub fn new(waker: &'a Waker, executor: &'a mut Executor) -> Context<'a> + + /// Get the `Waker` associated with the current task. + pub fn waker(&self) -> &Waker; + + /// Run an asynchronous computation to completion on the default executor. + /// + /// # Panics + /// + /// This method will panic if the default executor is unable to spawn. + /// To handle executor errors, use the `executor` method instead. + pub fn spawn(&mut self, f: impl Async + 'static + Send); + + /// Get the default executor associated with this task. + /// + /// This method is useful primarily if you want to explicitly handle + /// spawn failures. + /// + /// NB: this will remain unstable until the final `Executor` trait is ready. + pub fn executor(&mut self) -> &mut BoxExecutor; +} +``` + +Note that the `spawn` method here will box until `Executor` is added. + +## `core::ops` module + +With all of the above task infrastructure in place, defining `Async` is +straightforward. Since it's a "lang item", i.e. a trait that the language itself +treats specially when compiling `async` blocks, the trait goes into the +`core::ops` module, much as with the `Fn` family of traits. + +```rust +pub trait Async { + /// The type of value produced on completion. + type Output; + + /// Attempt to resolve the computation to a final value, registering + /// the current task for wakeup if the value is not yet available. + /// + /// # Return value + /// + /// This function returns: + /// + /// - `Poll::Pending` if the value is not ready yet. + /// - `Poll::Ready(val)` with the result `val` upon completion. + /// + /// Once an async value has completed, clients should not `poll` it again. + /// + /// When an async value is not ready yet, `poll` returns `Poll::Pending`. + /// The computation will *also* register the interest of the current task in the + /// value being produced. For example, if the async value represents the availability + /// of data on a socket, then the task is recorded so that when data arrives, + /// it is woken up (via `cx.waker()`). Once a task has been woken up, + /// it should attempt to `poll` the computation again, which may or may not + /// produce a final value at that time. + /// + /// Note that if `Pending` is returned it only means that the *current* task + /// (represented by the argument `cx`) will receive a notification. Tasks + /// from previous calls to `poll` will *not* receive notifications. + /// + /// # Runtime characteristics + /// + /// `Async` values are *inert*; they must be *actively* `poll`ed to make + /// progress, meaning that each time the current task is woken up, it should + /// actively re-`poll` pending computations that it still has an interest in. + /// Usually this is handled automatically by `async`/`await` notation or + /// via adapter methods. Executors ensure that each task is `poll`ed every + /// time a future internal to that task is ready to make progress. + /// + /// The `poll` function is not called repeatedly in a tight loop, but only + /// whenever the computation itself is ready to make progress, as signaled via + /// `cx.waker()`. If you're familiar with the `poll(2)` or `select(2)` + /// syscalls on Unix it's worth noting that async values typically do *not* + /// suffer the same problems of "all wakeups must poll all events"; they + /// are more like `epoll(4)`. + /// + /// An implementation of `poll` should strive to return quickly, and should + /// *never* block. Returning quickly prevents unnecessarily clogging up + /// threads or event loops. If it is known ahead of time that a call to + /// `poll` may end up taking awhile, the work should be offloaded to a + /// thread pool (or something similar) to ensure that `poll` can return + /// quickly. + /// + /// # Panics + /// + /// Once an async value has completed (returned `Ready` from `poll`), subsequent + /// calls to `poll` may panic, block forever, or otherwise cause bad behavior. + /// The `Async` trait itself provides no guarantees about the behavior of + /// `poll` after a future has completed. + fn poll(self: Pin, cx: &mut task::Context) -> Poll; +} +``` + +Most of the explanation here follows what we've already said about the task +system. The one twist is the use of `Pin`, which makes it possible to keep data +borrowed across separate calls to `poll` (i.e., "borrowing over yield +points"). The mechanics of pinning are explained +in [the RFC that introduced it](https://github.com/rust-lang/rfcs/pull/2349), +and the interoperation with traditional futures is described next. + +## Relation to the futures crate + +In many respects this RFC simply imports a minimal slice of +the [futures 0.2 API](http://aturon.github.io/2018/02/27/futures-0-2-RC/) into +libcore, and indeed +an [earlier iteration](https://github.com/rust-lang/rfcs/pull/2395) was +explicitly just that. This incarnation, however, focuses squarely on the +absolute minimal footprint needed to provide async/await support. + +It's thus out of scope for this RFC to say *precisely* what the futures 0.3 APIs +will look like. However, in broad outlines: + +- The 0.3 release will continue to provide a `Future` trait that resembles the + one in 0.2. In particular, the trait will *not* use `Pin`, and hence will + continue to work with today's stable Rust. (You can think of `Future` roughly + as an alias for `Async + Unpin`). + +- When a `nightly` flag is enabled, the crate will re-export the libcore version + of the task system rather than defining its own. + + - The nightly flag will include mechanisms for going between `Future` and `Async` + traits; any `Future` is trivially `Async`, and any `Async` value within a + `PinBox` can implement `Future`. + + - It will also provide combinators for `Async` values, including select and join, + so that these values can be composed without going through boxing/`Future`. + +The upshot is: + +- Futures 0.3 can be released very quickly and immedately work on stable Rust. +- Migrating to 0.3 should be as easy as migrating to 0.2. +- Code that works with futures 0.3 will be compatible with async/await when used + on nightly, allowing for experimentation while using the futures ecosystem. +- The futures API remains "out of tree", allowing for further iteration even + as `Async` and async/await notation are stabilized. + +The downside is that using `Async` values with code that expects a `Future` will +require boxing for the time being. + +In the long run, as we strengthen support and abstractions for working directly +with `Pin` values, it may turn out that there's little reason to avoid working +with `Async`/`Pin` directly, in which case we can deprecate `Future` and move +the ecosystem toward `Async`. However, the strategy proposed here gives us time +and leeway to investigate this question decoupled from shipping async/await +itself. + +The question of whether to build in an `Error` type for `Future`, and other such +details, will be tackled more directly in the futures repo. + +## Stabilization plan + +The ultimate goal is to ship async/await as part of Rust 2018 (roughly by +mid-September). + +This RFC is designed to migrate to a version of futures that can work with +async/await. In particular, foundational crates in the ecosystem (like Tokio and +Hyper) can continue working on *stable* Rust and the futures crate, while +*clients* of those crates can opt in to nightly to start using async/await. If +we can move quickly on these foundational crates, we should be able to have +several additional months of testing with async/await while still shipping in +the new edition. + +The task system has existed in roughly the proposed shape for quite a long time +in the futures crate, and has thus already been quite thoroughly vetted. + +The `Async` design is newer, but is closely related to `Future` in the futures +0.2 crate, which has seen very heavy usage in at least Google's Fuchsia OS. The +main differences from 0.1 are the use of an explicit task context argument +(rather than thread-local storage) and not baking in `Result`. But it's possible +to provide a futures 0.1-style API on top, if for some reason that is deemed +necessary. Thus, `Async` is also low-risk to stabilize. + +Probably the biggest open question for stabilization is the `Pin` +type. Stabilizing `Async` wholesale would require stabilizing `Pin`, but if for +some reason we are not comfortable doing so for the 2018 edition, there's a very +simple way to punt: + +```rust +// Essentially `PinBox>` +struct BoxAsync { .. } + +impl BoxAsync { + fn new>(a: A) -> BoxAsync; + fn poll(&mut self, cx: &mut task::Context) -> Poll; +} +``` + +That is, we can provide an `&mut`-based polling API that's tied to boxing +`Async` values, and provide a way to put them into boxes, without stabilizing +`Pin` or even `Async` itself. + +In short, it should be quite plausible to stabilize async/await in time for the +2018 edition given that the minimal such stabilization covers mechanisms and +APIs that have either already been thoroughly vetted, or are minimal commitment. + +## Details for `no_std` compatibility + +The APIs proposed above are almost entirely compatible with `core`, except for a +couple of constructors that require `std` objects: + +- Constructing a `Waker` from an `Arc: Wake` +- Constructing a `TaskObj` from a future + +These both have a similar shape: we have a concrete but opaque type (`Waker`, +`TaskObj`) that represents a trait object, but does *not* force a particular +*representation* for the trait object. In `std` environments, you can largely +gloss over this point and just use `Arc` or `Box` respectively. But internally, +the `Waker` and `TaskObj` types are more abstract. + +We'll look at the `Waker` case in detail. The idea is to provide an `UnsafeWake` +trait which represents "an arbitrary `Wake`-like trait object": + +```rust +/// An unsafe trait for implementing custom memory management for a +/// `Waker`. +/// +/// A `Waker` conceptually is a cloneable trait object for `Wake`, and is +/// most often essentially just `Arc: Wake`. However, in some contexts +/// (particularly `no_std`), it's desirable to avoid `Arc` in favor of some +/// custom memory management strategy. This trait is designed to allow for such +/// customization. +/// +/// A default implementation of the `UnsafeWake` trait is provided for the +/// `Arc` type in the standard library. +pub unsafe trait UnsafeWake { + /// Creates a new `Waker` from this instance of `UnsafeWake`. + /// + /// This function will create a new uniquely owned handle that under the + /// hood references the same notification instance. In other words calls + /// to `wake` on the returned handle should be equivalent to calls to + /// `wake` on this handle. + /// + /// # Unsafety + /// + /// This function is unsafe to call because it's asserting the `UnsafeWake` + /// value is in a consistent state, i.e. hasn't been dropped. + unsafe fn clone_raw(self: *mut Self) -> Waker; + + /// Drops this instance of `UnsafeWake`, deallocating resources + /// associated with it. + /// + /// # Unsafety + /// + /// This function is unsafe to call because it's asserting the `UnsafeWake` + /// value is in a consistent state, i.e. hasn't been dropped + unsafe fn drop_raw(self: *mut Self); + + /// Indicates that the associated task is ready to make progress and should + /// be `poll`ed. + /// + /// Executors generally maintain a queue of "ready" tasks; `wake` should place + /// the associated task onto this queue. + /// + /// # Panics + /// + /// Implementations should avoid panicking, but clients should also be prepared + /// for panics. + /// + /// # Unsafety + /// + /// This function is unsafe to call because it's asserting the `UnsafeWake` + /// value is in a consistent state, i.e. hasn't been dropped + unsafe fn wake(self: *mut self); +} +``` + +We then provide the following constructor for `Waker`: + +```rust +impl Waker { + pub unsafe fn new(inner: *const dyn UnsafeWake) -> Waker; +} +``` + +and a `From>` (where `Arc: Wake`) impl that uses it. + +## Task-local storage + +This RFC does not propose any implicit, built-in task-local storage. (Explicit +storage is always possible). + +Task-local storage is implementable on top of the proposed APIs by wrapping a +task in a *scoped* use of *thread*-local storage. When polling the task, a +thread-local value is established and hence usable implicitly within the call +chain. But when returning -- which also happens when the task is blocked -- the +thread-local is moved back out and stored with the task. + +In the future, we anticipate adding "spawn hooks" for the `Context::spawn` +method, essentially allowing you to guarantee that tasks spawned within some +scope are wrapped in some way. That's a separately useful feature, but it can in +particular be used to implement inheritance of task-local data. + +It may be that eventually we do want to build in some task-local data scheme, but: + +- The no_std story is unclear. +- There are a lot of possible designs around things like typemaps and + inheritance, and so it seems best for this work to begin in the ecosystem + first. + +# Drawbacks +[drawbacks]: #drawbacks + +This RFC is one of the most substantial additions to `std` proposed since +1.0. It commits us to including a particular task and polling model in the +standard library. The stakes are high. + +However, as argued in the stabilization section above, the meat of the proposal +has at this point already been thoroughly vetted; the core ideas go back about +two years at this point. It's possible to carve an extremely minimal path to +stabilization that essentially sticks to these already-proven ideas. Likewise, +async/await support (via generators) has already existing on the nightly channel +for quite a long time. + +So far we've been able to push the task/polling model into virtually every niche +Rust wishes to occupy, and the main downside has been, in essence, the lack of +async/await syntax (and +the +[borrowing it supports](http://aturon.github.io/2018/04/24/async-borrowing/)). + +# Rationale and alternatives +[alternatives]: #alternatives + +This RFC does not attempt to provide a complete introduction to the task model +that originated with the futures crate. A fuller account of the design rationale +and alternatives can be found in the following two blog posts: + +- [Zero-cost futures in Rust](http://aturon.github.io/2016/08/11/futures/) +- [Designing futures for Rust](http://aturon.github.io/2016/09/07/futures-design/) + +To summarize, the main alternative model for futures is a callback-based approach, +which was attempted for several months before the current approach was discovered. +In our experience, the callback approach suffered from several drawbacks in Rust: + +- It forced allocation almost everywhere, and hence was not compatible with no_std. +- It made cancellation *extremely* difficult to get right, whereas with the + proposed model it's just "drop". +- Subjectively, the combinator code was quite hairy, while with the task-based model + things fell into place quickly and easily. + +Some additional context and rationale is available in the [companion RFC]. + +## Alternative no_std handling + +Rather than using the `UnsafeWake` trait, we could factor "abstract `Arc`-like +trait objects" out as a first-class concept, `ArcObj`. We would also define an +`ArcLike` trait to determine what concrete types can fit into it: + +```rust +// An `Arc`-like trait object for a trait `T` +// +// Implements `Send`, `Sync` and `Clone` +struct ArcObj { + inner: *mut T, + + // a manually-constructed vtable for the Arc-like methods + drop_fn: unsafe fn(*mut T), + clone_fn: unsafe fn(*mut T) -> ArcObj, +} + +unsafe impl Send for ArcObj {} +unsafe impl Sync for ArcObj {} + +impl Deref for ArcObj { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.inner } + } +} + +// An object that can be used like `Arc` +unsafe trait ArcLike: Send + Sync { + fn into_raw(self) -> *mut T; + unsafe fn drop_fn(*mut T); + unsafe fn clone_fn(*mut T) -> ArcObj; +} + +unsafe impl ArcLike for Arc { + fn into_raw(self) -> *mut T { + Arc::into_raw(self) as *mut T + } + + unsafe fn drop_fn(t: *mut T) { + drop(Arc::from_raw(t)); + } + + unsafe fn clone_fn(t: *mut T) -> ArcObj { + let val: Arc = Arc::from_raw(t); + let cloned = val.clone(); + mem::forget(val); + ArcObj::new(cloned) + } +} + +impl ArcObj { + fn new>(u: U) -> ArcObj { + ArcObj { + inner: u.into_raw(), + drop_fn: U::drop_fn, + clone_fn: U::clone_fn, + } + } +} + +impl Clone for ArcObj { + fn clone(&self) -> ArcObj { + unsafe { + (self.clone_fn)(self.inner) + } + } +} + +impl Drop for ArcObj { + fn drop(&mut self) { + unsafe { + (self.drop_fn)(self.inner) + } + } +} +``` + +With this setup, we can define `Waker` as: + +```rust +struct Waker { + obj: ArcObj, +} +``` + +and allow construction from *any* `ArcObj`, rather than just +`Arc`, without using `UnsafeWake`. + +However, this would involve `ArcObj` appearing in multiple places throughout the +API, rather than sequestering the niche case into just the `UnsafeWake` trait as +this RFC proposes. + +# Prior art +[prior-art]: #prior-art + +There is substantial prior art both with async/await notation and with futures +(aka promises) as a basis. The proposed futures API was influenced by Scala's +futures in particular, and is broadly similar to APIs in a variety of other +languages (in terms of the adapters provided). + +What's more unique about the model in this RFC is the use of tasks, rather than +callbacks. The RFC author is not aware of other *futures* libraries using this +technique, but it is a fairly well-known technique more generally in functional +programming. For a recent example, +see +[this paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2011/01/monad-par.pdf) on +parallelism in Haskell. What seems to be perhaps new with this RFC is the idea +of melding the "trampoline" technique with an explicit, open-ended task/wakeup +model. + +# Unresolved questions +[unresolved]: #unresolved-questions + +- The futures 0.3 API, including how we wish to handle `Result` (and whether + e.g. it should provide an `AsyncResult` trait as well). This discussion will + take place separately from the RFC. From 0ded7b7aba3a3bc6b4b1b22fe4982839904b1a6a Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Tue, 29 May 2018 19:52:17 -0700 Subject: [PATCH 02/22] Revamp RFC to one-trait approach --- text/0000-async.md | 378 +++++++++++++++++++++++++++++++-------------- 1 file changed, 260 insertions(+), 118 deletions(-) diff --git a/text/0000-async.md b/text/0000-async.md index 0687d969da6..dc7e3450e01 100644 --- a/text/0000-async.md +++ b/text/0000-async.md @@ -12,14 +12,13 @@ smallest set of mechanisms needed to support async/await with borrowing and interoperation with the futures crate. Those mechanisms are: - The task system of the futures crate, which will be moved into `libcore` -- A new `Async` trait, which integrates the [`Pin` APIs][pin] with the task system to +- A new `Future` trait, which integrates the [`PinMut` APIs][pin] with the task system to provide async values that can interoperate with futures. [pin]: https://github.com/rust-lang/rfcs/pull/2349 [companion RFC]: https://github.com/rust-lang/rfcs/pull/2394 -The RFC also covers the intended ecosystem migration path, as well as the -possibility of eventually deprecating `Future` in favor of `Async`. +The RFC also covers the intended ecosystem migration path. # Motivation [motivation]: #motivation @@ -45,8 +44,8 @@ fn function<'a>(argument: &'a str) -> _Anonymous<'a, usize> { ``` Again like a closure the anonymous type is only usable through the trait it -implements: `Async`. The goal of this RFC is to provide a concrete proposal for -this `Async` trait, based on the work pioneered by the futures crate. +implements: `Future`. The goal of this RFC is to provide a concrete proposal for +this `Future` trait, based on the work pioneered by the futures crate. A secondary benefit of this RFC is that it enshrines the *task system* currently defined by the futures crate into `libcore`, thereby standardizing and @@ -56,40 +55,38 @@ mechanism. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -The `Async` trait represents an *asynchronous* and lazy computation that may +The `Future` trait represents an *asynchronous* and lazy computation that may eventually produce a final value, but doesn't have to block the current thread to do so. -Async values can be constructed through `async` blocks or `async` functions, e.g., +Futures can be constructed through `async` blocks or `async` functions, e.g., ```rust async fn read_frame(socket: &TcpStream) -> Result { ... } ``` -This `async` function, when invoked, produces an async value that represents the +This `async` function, when invoked, produces a future that represents the completion of reading a frame from the given socket. The function signature is equivalent to: ```rust fn read_frame<'sock>(socket: &'sock TcpStream) - -> impl Async> + 'sock; + -> impl Future> + 'sock; ``` -Other async functions can *await* this asynchronous value; see the [companion +Other async functions can *await* this future; see the [companion RFC] for full details. -In addition to `async fn` definitions, async values can be built using adapters on -the `Async` trait, much like with `Iterator`s. The standard library includes a -number of basic adapters (described in the reference below), while some -particularly interesting variants are iterating in the crates.io ecosystem -first. +In addition to `async fn` definitions, futures can be built using adapters, much +like with `Iterator`s. Initially these adapters will be provided entirely "out +of tree", but eventually they will make their way into the standard library. Ultimately asynchronous computations are executed by *tasks*, which are lightweight threads. In particular, an *executor* is able to "spawn" a -`()`-producing `Async` value as an independent task; these tasks are then +`()`-producing `Future` as an independent task; these tasks are then cooperatively scheduled onto one or more operating system threads. The `Executor` trait defines this interface, and the `task` module provides a host -of related definitions needed when manually implementing `Async` values or +of related definitions needed when manually implementing `Future`s or executors. # Reference-level explanation @@ -216,7 +213,7 @@ pub struct TaskObj { .. } impl TaskObj { /// Create a new `TaskObj` by boxing the given future. - pub fn new + Send + 'static>(f: A) -> TaskObj; + pub fn new + Send + 'static>(f: A) -> TaskObj; } /// Provides the reason that an executor was unable to spawn. @@ -249,7 +246,7 @@ we would deprecate `spawn_obj` and add a default `spawn` method: ```rust trait Executor { - fn spawn(&mut self, task: Async + Send) -> Result<(), SpawnErrorKind> { + fn spawn(&mut self, task: Future + Send) -> Result<(), SpawnErrorKind> { self.spawn_obj(TaskObj::new(task)) } // ... @@ -323,7 +320,7 @@ impl<'a> Context<'a> { /// /// This method will panic if the default executor is unable to spawn. /// To handle executor errors, use the `executor` method instead. - pub fn spawn(&mut self, f: impl Async + 'static + Send); + pub fn spawn(&mut self, f: impl Future + 'static + Send); /// Get the default executor associated with this task. /// @@ -337,15 +334,13 @@ impl<'a> Context<'a> { Note that the `spawn` method here will box until `Executor` is added. -## `core::ops` module +## `core::future` module -With all of the above task infrastructure in place, defining `Async` is -straightforward. Since it's a "lang item", i.e. a trait that the language itself -treats specially when compiling `async` blocks, the trait goes into the -`core::ops` module, much as with the `Fn` family of traits. +With all of the above task infrastructure in place, defining `Future` is +straightforward: ```rust -pub trait Async { +pub trait Future { /// The type of value produced on completion. type Output; @@ -359,14 +354,14 @@ pub trait Async { /// - `Poll::Pending` if the value is not ready yet. /// - `Poll::Ready(val)` with the result `val` upon completion. /// - /// Once an async value has completed, clients should not `poll` it again. + /// Once a future has completed, clients should not `poll` it again. /// - /// When an async value is not ready yet, `poll` returns `Poll::Pending`. - /// The computation will *also* register the interest of the current task in the - /// value being produced. For example, if the async value represents the availability + /// When a future is not ready yet, `poll` returns `Poll::Pending`. + /// The future will *also* register the interest of the current task in the + /// value being produced. For example, if the future represents the availability /// of data on a socket, then the task is recorded so that when data arrives, /// it is woken up (via `cx.waker()`). Once a task has been woken up, - /// it should attempt to `poll` the computation again, which may or may not + /// it should attempt to `poll` the future again, which may or may not /// produce a final value at that time. /// /// Note that if `Pending` is returned it only means that the *current* task @@ -375,7 +370,7 @@ pub trait Async { /// /// # Runtime characteristics /// - /// `Async` values are *inert*; they must be *actively* `poll`ed to make + /// `Future` values are *inert*; they must be *actively* `poll`ed to make /// progress, meaning that each time the current task is woken up, it should /// actively re-`poll` pending computations that it still has an interest in. /// Usually this is handled automatically by `async`/`await` notation or @@ -398,16 +393,16 @@ pub trait Async { /// /// # Panics /// - /// Once an async value has completed (returned `Ready` from `poll`), subsequent + /// Once a future has completed (returned `Ready` from `poll`), subsequent /// calls to `poll` may panic, block forever, or otherwise cause bad behavior. - /// The `Async` trait itself provides no guarantees about the behavior of + /// The `Future` trait itself provides no guarantees about the behavior of /// `poll` after a future has completed. - fn poll(self: Pin, cx: &mut task::Context) -> Poll; + fn poll(self: PinMut, cx: &mut task::Context) -> Poll; } ``` Most of the explanation here follows what we've already said about the task -system. The one twist is the use of `Pin`, which makes it possible to keep data +system. The one twist is the use of `PinMut`, which makes it possible to keep data borrowed across separate calls to `poll` (i.e., "borrowing over yield points"). The mechanics of pinning are explained in [the RFC that introduced it](https://github.com/rust-lang/rfcs/pull/2349), @@ -423,90 +418,159 @@ explicitly just that. This incarnation, however, focuses squarely on the absolute minimal footprint needed to provide async/await support. It's thus out of scope for this RFC to say *precisely* what the futures 0.3 APIs -will look like. However, in broad outlines: +will look like, but there are a few key aspects worth calling out. (Note that +over time, many of these APIs will be stabilized into `std`.) -- The 0.3 release will continue to provide a `Future` trait that resembles the - one in 0.2. In particular, the trait will *not* use `Pin`, and hence will - continue to work with today's stable Rust. (You can think of `Future` roughly - as an alias for `Async + Unpin`). +### Error handling -- When a `nightly` flag is enabled, the crate will re-export the libcore version - of the task system rather than defining its own. +It's very common to work with `Result`-producing futures, so the crate will +provide the following alias: - - The nightly flag will include mechanisms for going between `Future` and `Async` - traits; any `Future` is trivially `Async`, and any `Async` value within a - `PinBox` can implement `Future`. +```rust +/// A convenience for futures that return `Result` values that includes +/// a variety of adapters tailored to such futures. +pub trait TryFuture { + /// The type of successful values yielded by this future + type Item; - - It will also provide combinators for `Async` values, including select and join, - so that these values can be composed without going through boxing/`Future`. + /// The type of failures yielded by this future + type Error; -The upshot is: + /// Poll this `TryFuture` as if it were a `Future`. + /// + /// This method is a stopgap for a compiler limitation that prevents us from + /// directly inheriting from the `Future` trait; in the future it won't be + /// needed. + fn try_poll(self: PinMut, cx: &mut task::Context) -> Poll>; +} -- Futures 0.3 can be released very quickly and immedately work on stable Rust. -- Migrating to 0.3 should be as easy as migrating to 0.2. -- Code that works with futures 0.3 will be compatible with async/await when used - on nightly, allowing for experimentation while using the futures ecosystem. -- The futures API remains "out of tree", allowing for further iteration even - as `Async` and async/await notation are stabilized. +impl TryFuture for F + where F: Future> +{ + type Item = T; + type Error = E; -The downside is that using `Async` values with code that expects a `Future` will -require boxing for the time being. + fn try_poll(self: PinMut, cx: &mut task::Context) -> Poll { + self.poll(cx) + } +} +``` -In the long run, as we strengthen support and abstractions for working directly -with `Pin` values, it may turn out that there's little reason to avoid working -with `Async`/`Pin` directly, in which case we can deprecate `Future` and move -the ecosystem toward `Async`. However, the strategy proposed here gives us time -and leeway to investigate this question decoupled from shipping async/await -itself. +This alias makes it easy to require that a future return a `Result` (by bounding +by `TryFuture`), and to obtain the success/error types (by using the `Item` and +`Error` associated types). -The question of whether to build in an `Error` type for `Future`, and other such -details, will be tackled more directly in the futures repo. +Similarly, `PollResult` is a type alias for `Poll>`. -## Stabilization plan +### Combinators -The ultimate goal is to ship async/await as part of Rust 2018 (roughly by -mid-September). +The crate will provide extension traits, `FutureExt` and `TryFutureExt`, with a +full complement of combinators like those provided in futures 0.2. + +### Conveniences for unpinned futures + +The `FutureExt` trait will, in particular, provide a convenience for working +with `Unpin` types without having to use the pin APIs: + +```rust +trait FutureExt: Future { + fn poll_unpin(&mut self, cx: &mut task::Context) -> Poll + where Self: Unpin { ... } + + // ... +} +``` + +## Writing manual futures + +Not all async code will be written using `async`/`await`, and it's important to +retain a solid experience for manually implementing futures. + +Compared to the futures 0.2 experience, the main changes in this RFC are (1) the +use of `PinMut` and (2) no longer baking in an error type. In both cases, it's +straightforward, if a bit tedious to recover the previous programming model. + +Here's an example drawn from [tower-grpc]: + +[tower-grpc]: https://github.com/tower-rs/tower-grpc/blob/master/src/client/server_streaming.rs#L21-L32 -This RFC is designed to migrate to a version of futures that can work with -async/await. In particular, foundational crates in the ecosystem (like Tokio and -Hyper) can continue working on *stable* Rust and the futures crate, while -*clients* of those crates can opt in to nightly to start using async/await. If -we can move quickly on these foundational crates, we should be able to have -several additional months of testing with async/await while still shipping in -the new edition. - -The task system has existed in roughly the proposed shape for quite a long time -in the futures crate, and has thus already been quite thoroughly vetted. - -The `Async` design is newer, but is closely related to `Future` in the futures -0.2 crate, which has seen very heavy usage in at least Google's Fuchsia OS. The -main differences from 0.1 are the use of an explicit task context argument -(rather than thread-local storage) and not baking in `Result`. But it's possible -to provide a futures 0.1-style API on top, if for some reason that is deemed -necessary. Thus, `Async` is also low-risk to stabilize. - -Probably the biggest open question for stabilization is the `Pin` -type. Stabilizing `Async` wholesale would require stabilizing `Pin`, but if for -some reason we are not comfortable doing so for the 2018 edition, there's a very -simple way to punt: +```rust +// code written in futures 0.2 style + +impl Future for ResponseFuture +where T: Message + Default, + U: Future>, + B: Body, +{ + type Item = ::Response>; + type Error = ::Error; + + fn poll(&mut self, cx: &mut task::Context) -> Poll { + self.inner.poll(cx) + } +} +``` + +To port this code, we systematically employ a few steps: + +- Implementing `Unpin` for the manual future, which opts out of interior + borrowing. +- Use `TryFuture` for all bounds over futures, and return `PollResult` in `poll`. +- Call `poll_unpin` rather than `poll` when polling inner futures. + +All told, the ported code looks as follows: ```rust -// Essentially `PinBox>` -struct BoxAsync { .. } +// now ported to futures 0.3 + +impl Unpin for ResponseFuture {} + +impl Future for ResponseFuture +where T: Message + Default, + U: TryFuture> + Unpin, + B: Body, +{ + type Output = Result<::Response>, ::Error>; -impl BoxAsync { - fn new>(a: A) -> BoxAsync; - fn poll(&mut self, cx: &mut task::Context) -> Poll; + fn poll(self: PinMut, cx: &mut task::Context) -> PollResult { + self.inner.poll_unpin(cx) + } } ``` -That is, we can provide an `&mut`-based polling API that's tied to boxing -`Async` values, and provide a way to put them into boxes, without stabilizing -`Pin` or even `Async` itself. +These changes are mechanical enough that it's likely possible to write a script +to perform them with high accuracy. + +To be clear, however, there is a definite ergonomic hit here, which is discussed +further in the drawbacks section. -In short, it should be quite plausible to stabilize async/await in time for the -2018 edition given that the minimal such stabilization covers mechanisms and -APIs that have either already been thoroughly vetted, or are minimal commitment. +## Stabilization plan + +The ultimate goal is to ship async/await as part of Rust 2018 (roughly by +mid-September). + +Much of the design has been vetted over quite a long period (futures 0.1), and +the 0.2 changes have gotten substantial use in Google's Fuchsia OS. The task +system in particular has existed in roughly the proposed shape for quite a long +time in the futures crate, and has thus already been quite thoroughly vetted. + +Thus the major new elements are, once more, the removal of built-in errors and +the use of `PinMut`. + +- For built-in errors, the situation is straightforward: we can provide + equivalent functionality at mild ergonomic cost (an extra import). + +- For `PinMut`, there's a clear way to "opt out" and recover the previous + semantics (again at some ergonomic cost), so the primary questions come down + to the `PinMut` API itself. The core types have already received significant + vetting within the RustBelt formal model. In addition to the opt-out, there's + ongoing work in making `PinMut` ergonomic and safe to use directly (rather + than only through `async` notation). + +Tactically, the proposal is to produce a 0.3-beta release, which will be +nightly-only, and to work through support in as large a chunk of the ecosystem +as we can manage, by adding feature flags to crates to opt in to the new +version. ## Details for `no_std` compatibility @@ -612,19 +676,11 @@ It may be that eventually we do want to build in some task-local data scheme, bu inheritance, and so it seems best for this work to begin in the ecosystem first. -# Drawbacks -[drawbacks]: #drawbacks +# Rationale, drawbacks, and alternatives This RFC is one of the most substantial additions to `std` proposed since 1.0. It commits us to including a particular task and polling model in the -standard library. The stakes are high. - -However, as argued in the stabilization section above, the meat of the proposal -has at this point already been thoroughly vetted; the core ideas go back about -two years at this point. It's possible to carve an extremely minimal path to -stabilization that essentially sticks to these already-proven ideas. Likewise, -async/await support (via generators) has already existing on the nightly channel -for quite a long time. +standard library, and ties us to `PinMut`. So far we've been able to push the task/polling model into virtually every niche Rust wishes to occupy, and the main downside has been, in essence, the lack of @@ -632,9 +688,6 @@ async/await syntax (and the [borrowing it supports](http://aturon.github.io/2018/04/24/async-borrowing/)). -# Rationale and alternatives -[alternatives]: #alternatives - This RFC does not attempt to provide a complete introduction to the task model that originated with the futures crate. A fuller account of the design rationale and alternatives can be found in the following two blog posts: @@ -652,7 +705,98 @@ In our experience, the callback approach suffered from several drawbacks in Rust - Subjectively, the combinator code was quite hairy, while with the task-based model things fell into place quickly and easily. -Some additional context and rationale is available in the [companion RFC]. +Some additional context and rationale for the overall async/await project is +available in the [companion RFC]. + +For the remainder of this section, we'll dive into specific API design questions +where this RFC differs from futures 0.2. + +## Rationale, drawbacks and alternatives for removing built-in errors + +There are an assortment of reasons to drop the built-in error type in the main +trait: + +- **Improved type checking and inference**. The error type is one of the biggest + pain points when working with futures combinators today, both in trying to get + different types to match up, and in inference failures that result when a + piece of code cannot produce an error. To be clear, many of these problems + will become less pronounced when `async` syntax is available. + +- **Async functions**. If we retain a built-in error type, it's much less clear + how `async fn` should work: should it always require the return type to be a + `Result`? If not, what happens when a non-`Result` type is returned? + +- **Combinator clarity**. Splitting up the combinators by whether they rely on + errors or not clarifies the semantics. This is *especially* true for streams, + where error handling is a common source of confusion. + +- **Orthogonality**. In general, producing and handling errors is separable from + the core polling mechanism, so all things being equal, it seems good to follow + Rust's general design principles and treat errors by *composing* with `Result`. + +All of that said, there are real downsides for error-heavy code, even with +`TryFuture`: + +- An extra import is needed (obviated if code imports the futures prelude, which + we could perhaps more vocally encourage). + +- It can be confusing for code to *bound* by one trait but *implement* another. + +The error handling piece of this RFC is separable from the other pieces, so the +main alternative would be to retain the built-in error type. + +## Rationale, drawbacks and alternatives to the core trait design (wrt `PinMut`) + +Putting aside error handling, which is orthogonal and discussed above, the +primary other big item in this RFC is the move to `PinMut` for the core polling +method, and how it relates to `Unpin`/manually-written futures. Over the course +of RFC discussions, we've identified essentially three main approaches to this +question: + +- **One core trait**. That's the approach taken in the main RFC text: there's + just a single core `Future` trait, which works on `PinMut`. Separately + there's a `poll_unpin` helper for working with `Unpin` futures in manual + implementations. + +- **Two core traits**. We can provide two traits, for example `MoveFuture` and + `Future`, where one operates on `&mut self` and the other on `PinMut`. + This makes it possible to continue writing code in the futures 0.2 style, + i.e. without importing `PinMut`/`Unpin` or otherwise talking about pins. A + critical requirement is the need for interoperation, so that a `MoveFuture` + can be used anywhere a `Future` is required. There are at least two ways to + achieve such interop: + + - Via a blanket impl of `Future` for `T: MoveFuture`. This approach currently + blocks some *other* desired impls (around `Box` and `&mut` specifically), + but the problem doesn't appear to be fundamental. + + - Via a subtrait relationship, so that `T: Future` is defined essentially as + an alias for `for<'a> PinMut<'a, T>: MoveFuture`. Unfortunately, such + "higher ranked" trait relationships don't currently work well in the trait + system, and this approach also makes things more convoluted when + implementing `Future` by hand, for relatively little gain. + +The drawback of the "one core trait" approach taken by this RFC is its ergonomic +hit when writing moveable futures by hand: you now need to import `PinMut` and +`Unpin`, invoke `poll_unpin`, and impl `Unpin` for your types. This is all +pretty mechanical, but it's a pain. It's possible that improvements in `PinMut` +ergonomics will obviate some of these issues, but there are a lot of open +questions there still. + +On the other hand, a two-trait approach has downsides as well. If we *also* +remove the error type, there's a combinatorial explosion, since we end up +needing `Try` variants of each trait (and this extends to related traits, like +`Stream`, as well). More broadly, with the one-trait approach, `Unpin` acts as a +kind of "independent knob" that can be applied orthogonally from other concerns; +with the two-trait approach, it's "mixed in". And both of the two-trait +approaches run up against compiler limitations at the moment, though of course +that shouldn't be taken as a deciding factor. + +**The primary reason this RFC opts for the one-trait approach is that it's the +conservative, forward-compatible option**. It's possible to add `MoveFuture`, +together with a blanket impl, at any point in the future. Thus, starting with +just the single `Future` trait as proposed in this RFC keeps our options +maximally open while we gain experience. ## Alternative no_std handling @@ -769,6 +913,4 @@ model. # Unresolved questions [unresolved]: #unresolved-questions -- The futures 0.3 API, including how we wish to handle `Result` (and whether - e.g. it should provide an `AsyncResult` trait as well). This discussion will - take place separately from the RFC. +None at the moment. From 01ce3f94b0f3bafc140e5666a70347018e137f27 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 9 Nov 2018 15:05:29 -0800 Subject: [PATCH 03/22] Revamp for stabilization --- text/0000-async.md | 916 ------------------------------------------- text/0000-futures.md | 497 +++++++++++++++++++++++ 2 files changed, 497 insertions(+), 916 deletions(-) delete mode 100644 text/0000-async.md create mode 100644 text/0000-futures.md diff --git a/text/0000-async.md b/text/0000-async.md deleted file mode 100644 index dc7e3450e01..00000000000 --- a/text/0000-async.md +++ /dev/null @@ -1,916 +0,0 @@ -- Feature Name: (fill me in with a unique ident, my_awesome_feature) -- Start Date: 2018-04-04 -- RFC PR: (leave this empty) -- Rust Issue: (leave this empty) - -# Summary -[summary]: #summary - -This RFC provides the library component for the first-class `async`/`await` -syntax proposed in a [companion RFC]. It is intentionally minimal, including the -smallest set of mechanisms needed to support async/await with borrowing and -interoperation with the futures crate. Those mechanisms are: - -- The task system of the futures crate, which will be moved into `libcore` -- A new `Future` trait, which integrates the [`PinMut` APIs][pin] with the task system to - provide async values that can interoperate with futures. - -[pin]: https://github.com/rust-lang/rfcs/pull/2349 -[companion RFC]: https://github.com/rust-lang/rfcs/pull/2394 - -The RFC also covers the intended ecosystem migration path. - -# Motivation -[motivation]: #motivation - -The basic motivation for this RFC is to provide a supporting mechanism for -`async`/`await` syntax: - -```rust -async fn function(argument: &str) -> usize { - // ... -} -``` - -The syntax itself is motivated in the [companion RFC], and there is -a [blog post](http://aturon.github.io/2018/04/24/async-borrowing/) that goes -through its importance in greater detail. As with closures, the syntax involves -producing an anonymous type, so that the above declaration is equivalent to: - -```rust -fn function<'a>(argument: &'a str) -> _Anonymous<'a, usize> { - // ... -} -``` - -Again like a closure the anonymous type is only usable through the trait it -implements: `Future`. The goal of this RFC is to provide a concrete proposal for -this `Future` trait, based on the work pioneered by the futures crate. - -A secondary benefit of this RFC is that it enshrines the *task system* currently -defined by the futures crate into `libcore`, thereby standardizing and -ultimately stabilizing the async ecosystem around a single lightweight task -mechanism. - -# Guide-level explanation -[guide-level-explanation]: #guide-level-explanation - -The `Future` trait represents an *asynchronous* and lazy computation that may -eventually produce a final value, but doesn't have to block the current thread -to do so. - -Futures can be constructed through `async` blocks or `async` functions, e.g., - -```rust -async fn read_frame(socket: &TcpStream) -> Result { ... } -``` - -This `async` function, when invoked, produces a future that represents the -completion of reading a frame from the given socket. The function signature -is equivalent to: - -```rust -fn read_frame<'sock>(socket: &'sock TcpStream) - -> impl Future> + 'sock; -``` - -Other async functions can *await* this future; see the [companion -RFC] for full details. - -In addition to `async fn` definitions, futures can be built using adapters, much -like with `Iterator`s. Initially these adapters will be provided entirely "out -of tree", but eventually they will make their way into the standard library. - -Ultimately asynchronous computations are executed by *tasks*, which are -lightweight threads. In particular, an *executor* is able to "spawn" a -`()`-producing `Future` as an independent task; these tasks are then -cooperatively scheduled onto one or more operating system threads. The -`Executor` trait defines this interface, and the `task` module provides a host -of related definitions needed when manually implementing `Future`s or -executors. - -# Reference-level explanation -[reference-level-explanation]: #reference-level-explanation - -## `core::task` module - -The fundamental mechanism for asynchronous computation in Rust is *tasks*, which -are lightweight threads of execution; many tasks can be cooperatively scheduled -onto a single operating system thread. - -To perform this cooperative scheduling we use a technique sometimes referred to -as a "trampoline". When a task would otherwise need to block waiting for some -event, instead it schedules itself for later wakeup and *returns* to the -executor running it, which can then run another task. Subsequent wakeups place -the task back on the executors queue of ready tasks, much like a thread -scheduler in an operating system. - -Attempting to complete a task (or async value within it) is called *polling*, -and always yields a `Poll` value back: - -```rust -/// Indicates whether a value is available, or if the current task has been -/// scheduled for later wake-up instead. -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum Poll { - /// Represents that a value is immediately ready. - Ready(T), - - /// Represents that a value is not ready yet. - /// - /// When a function returns `Pending`, the function *must* also - /// ensure that the current task is scheduled to be awoken when - /// progress can be made. - Pending, -} -``` - -When a task returns `Poll::Ready`, the executor knows the task has completed and -can be dropped. - -### Waking up - -Each task executor provides its own scheduling facilities, and hence needs to -customize the way task wakeups are handled. As such, there is a -`std::task::Wake` trait defining wakeup behavior: - -```rust -/// A way of waking up a specific task. -/// -/// Any task executor must provide a way of signaling that a task it owns -/// is ready to be `poll`ed again. Executors do so by providing a wakeup handle -/// type that implements this trait. -pub trait Wake: Send + Sync { - /// Indicates that the associated task is ready to make progress and should - /// be `poll`ed. - /// - /// Executors generally maintain a queue of "ready" tasks; `wake` should place - /// the associated task onto this queue. - fn wake(&self); -} -``` - -In general async values are not coupled to any particular executor, so we use a trait -object to handle waking: - -```rust -/// A `Waker` is a handle for waking up a task by notifying its executor that it -/// is ready to be run. -/// -/// This handle contains a trait object pointing to an instance of the `UnsafeWake` -/// trait, allowing notifications to get routed through it. -/// -/// Implements `Clone`, `Send`, and `Sync`. -pub struct Waker { ... } - -impl Waker { - /// Wake up the task associated with this `Waker`. - pub fn wake(&self); -} - -// We will see how to handle the no_std case later in the RFC... -impl From for Waker where Arc: Wake + 'static { ... } -``` - -Task execution always happens in the context of a `Waker` that can be used to -wake the task up; we'll see the full `core::task::Context` structure below. - -### Executors - -An executor is responsible for polling tasks to completion. We represent this -with the `core::task::Executor` trait: - -```rust -/// A task executor. -/// -/// A *task* is a `()`-producing async value that runs at the top level, and will -/// be `poll`ed until completion. It's also the unit at which wake-up -/// notifications occur. Executors, such as thread pools, allow tasks to be -/// spawned and are responsible for putting tasks onto ready queues when -/// they are woken up, and polling them when they are ready. -pub trait Executor { - /// Spawn the given task, polling it until completion. - /// - /// # Errors - /// - /// The executor may be unable to spawn tasks, either because it has - /// been shut down or is resource-constrained. - fn spawn_obj(&mut self, task: TaskObj) -> Result<(), SpawnObjError>; - - /// Determine whether the executor is able to spawn new tasks. - /// - /// # Returns - /// - /// An `Ok` return means the executor is *likely* (but not guaranteed) - /// to accept a subsequent spawn attempt. Likewise, an `Err` return - /// means that `spawn` is likely, but not guaranteed, to yield an error. - fn status(&self) -> Result<(), SpawnErrorKind> { - Ok(()) - } -} - -pub struct TaskObj { .. } - -impl TaskObj { - /// Create a new `TaskObj` by boxing the given future. - pub fn new + Send + 'static>(f: A) -> TaskObj; -} - -/// Provides the reason that an executor was unable to spawn. -pub struct SpawnErrorKind { .. } - -impl SpawnErrorKind { - /// Spawning is failing because the executor has been shut down. - pub fn shutdown() -> SpawnErrorKind; - - /// Check whether this error is the `shutdown` error. - pub fn is_shutdown(&self) -> bool; - - // additional error variants added over time... -} - -/// The result of a failed spawn -pub struct SpawnObjError { - /// The kind of error - pub kind: SpawnErrorKind, - - /// The task for which spawning was attempted - pub task: TaskObj, -} -``` - -We need the executor trait to be usable as a trait object, which is why `TaskObj` -is constructed here from a boxed future. (In the no_std section, we'll see -another constructor). In the long run, though, once we can take `dyn` by value, -we would deprecate `spawn_obj` and add a default `spawn` method: - -```rust -trait Executor { - fn spawn(&mut self, task: Future + Send) -> Result<(), SpawnErrorKind> { - self.spawn_obj(TaskObj::new(task)) - } - // ... -} -``` - -At that point we would also deprecate `TaskObj`, which is the reason for using -the `Obj` suffix -- we want to keep the name `Task` available for potential -usage down the line. - -In addition to the above, the `core::task` module will include the following API -for helping detect bugs: - -```rust -/// Marks the current thread as being within the dynamic extent of an -/// executor. -/// -/// Executor implementations should call this function before beginning to -/// execute a tasks, and drop the returned `Enter` value after completing -/// task execution: -/// -/// ```rust -/// let enter = enter().expect("..."); -/// /* run task */ -/// drop(enter); -/// ``` -/// -/// Doing so ensures that executors aren't accidentally invoked in a nested fashion. -/// When that happens, the inner executor can block waiting for an event that can -/// only be triggered by the outer executor, leading to a deadlock. -/// -/// # Error -/// -/// Returns an error if the current thread is already marked, in which case the -/// caller should panic with a tailored error message. -pub fn enter() -> Result -``` - -As stated in the doc comment, the expectation is that all executors will wrap -their task execution within an `enter` to detect inadvertent nesting. - -### Task contexts - -All tasks are executed with two pieces of contextual information: - -- A `Waker` for waking the task up later on. -- An executor, which is the "default" place to spawn further tasks. - -Notably, this list does *not* include task-local data; that can be addressed -externally, as we'll see in a later section. - -The `core::task::Context` type gathers the (stack-rooted) contextual information -together, and is passed by mutable reference to all polling functions: - -```rust -/// Information about the currently-running task. -/// -/// Contexts are always tied to the stack, since they are set up specifically -/// when performing a single `poll` step on a task. -pub struct Context<'a> { .. } - -impl<'a> Context<'a> { - pub fn new(waker: &'a Waker, executor: &'a mut Executor) -> Context<'a> - - /// Get the `Waker` associated with the current task. - pub fn waker(&self) -> &Waker; - - /// Run an asynchronous computation to completion on the default executor. - /// - /// # Panics - /// - /// This method will panic if the default executor is unable to spawn. - /// To handle executor errors, use the `executor` method instead. - pub fn spawn(&mut self, f: impl Future + 'static + Send); - - /// Get the default executor associated with this task. - /// - /// This method is useful primarily if you want to explicitly handle - /// spawn failures. - /// - /// NB: this will remain unstable until the final `Executor` trait is ready. - pub fn executor(&mut self) -> &mut BoxExecutor; -} -``` - -Note that the `spawn` method here will box until `Executor` is added. - -## `core::future` module - -With all of the above task infrastructure in place, defining `Future` is -straightforward: - -```rust -pub trait Future { - /// The type of value produced on completion. - type Output; - - /// Attempt to resolve the computation to a final value, registering - /// the current task for wakeup if the value is not yet available. - /// - /// # Return value - /// - /// This function returns: - /// - /// - `Poll::Pending` if the value is not ready yet. - /// - `Poll::Ready(val)` with the result `val` upon completion. - /// - /// Once a future has completed, clients should not `poll` it again. - /// - /// When a future is not ready yet, `poll` returns `Poll::Pending`. - /// The future will *also* register the interest of the current task in the - /// value being produced. For example, if the future represents the availability - /// of data on a socket, then the task is recorded so that when data arrives, - /// it is woken up (via `cx.waker()`). Once a task has been woken up, - /// it should attempt to `poll` the future again, which may or may not - /// produce a final value at that time. - /// - /// Note that if `Pending` is returned it only means that the *current* task - /// (represented by the argument `cx`) will receive a notification. Tasks - /// from previous calls to `poll` will *not* receive notifications. - /// - /// # Runtime characteristics - /// - /// `Future` values are *inert*; they must be *actively* `poll`ed to make - /// progress, meaning that each time the current task is woken up, it should - /// actively re-`poll` pending computations that it still has an interest in. - /// Usually this is handled automatically by `async`/`await` notation or - /// via adapter methods. Executors ensure that each task is `poll`ed every - /// time a future internal to that task is ready to make progress. - /// - /// The `poll` function is not called repeatedly in a tight loop, but only - /// whenever the computation itself is ready to make progress, as signaled via - /// `cx.waker()`. If you're familiar with the `poll(2)` or `select(2)` - /// syscalls on Unix it's worth noting that async values typically do *not* - /// suffer the same problems of "all wakeups must poll all events"; they - /// are more like `epoll(4)`. - /// - /// An implementation of `poll` should strive to return quickly, and should - /// *never* block. Returning quickly prevents unnecessarily clogging up - /// threads or event loops. If it is known ahead of time that a call to - /// `poll` may end up taking awhile, the work should be offloaded to a - /// thread pool (or something similar) to ensure that `poll` can return - /// quickly. - /// - /// # Panics - /// - /// Once a future has completed (returned `Ready` from `poll`), subsequent - /// calls to `poll` may panic, block forever, or otherwise cause bad behavior. - /// The `Future` trait itself provides no guarantees about the behavior of - /// `poll` after a future has completed. - fn poll(self: PinMut, cx: &mut task::Context) -> Poll; -} -``` - -Most of the explanation here follows what we've already said about the task -system. The one twist is the use of `PinMut`, which makes it possible to keep data -borrowed across separate calls to `poll` (i.e., "borrowing over yield -points"). The mechanics of pinning are explained -in [the RFC that introduced it](https://github.com/rust-lang/rfcs/pull/2349), -and the interoperation with traditional futures is described next. - -## Relation to the futures crate - -In many respects this RFC simply imports a minimal slice of -the [futures 0.2 API](http://aturon.github.io/2018/02/27/futures-0-2-RC/) into -libcore, and indeed -an [earlier iteration](https://github.com/rust-lang/rfcs/pull/2395) was -explicitly just that. This incarnation, however, focuses squarely on the -absolute minimal footprint needed to provide async/await support. - -It's thus out of scope for this RFC to say *precisely* what the futures 0.3 APIs -will look like, but there are a few key aspects worth calling out. (Note that -over time, many of these APIs will be stabilized into `std`.) - -### Error handling - -It's very common to work with `Result`-producing futures, so the crate will -provide the following alias: - -```rust -/// A convenience for futures that return `Result` values that includes -/// a variety of adapters tailored to such futures. -pub trait TryFuture { - /// The type of successful values yielded by this future - type Item; - - /// The type of failures yielded by this future - type Error; - - /// Poll this `TryFuture` as if it were a `Future`. - /// - /// This method is a stopgap for a compiler limitation that prevents us from - /// directly inheriting from the `Future` trait; in the future it won't be - /// needed. - fn try_poll(self: PinMut, cx: &mut task::Context) -> Poll>; -} - -impl TryFuture for F - where F: Future> -{ - type Item = T; - type Error = E; - - fn try_poll(self: PinMut, cx: &mut task::Context) -> Poll { - self.poll(cx) - } -} -``` - -This alias makes it easy to require that a future return a `Result` (by bounding -by `TryFuture`), and to obtain the success/error types (by using the `Item` and -`Error` associated types). - -Similarly, `PollResult` is a type alias for `Poll>`. - -### Combinators - -The crate will provide extension traits, `FutureExt` and `TryFutureExt`, with a -full complement of combinators like those provided in futures 0.2. - -### Conveniences for unpinned futures - -The `FutureExt` trait will, in particular, provide a convenience for working -with `Unpin` types without having to use the pin APIs: - -```rust -trait FutureExt: Future { - fn poll_unpin(&mut self, cx: &mut task::Context) -> Poll - where Self: Unpin { ... } - - // ... -} -``` - -## Writing manual futures - -Not all async code will be written using `async`/`await`, and it's important to -retain a solid experience for manually implementing futures. - -Compared to the futures 0.2 experience, the main changes in this RFC are (1) the -use of `PinMut` and (2) no longer baking in an error type. In both cases, it's -straightforward, if a bit tedious to recover the previous programming model. - -Here's an example drawn from [tower-grpc]: - -[tower-grpc]: https://github.com/tower-rs/tower-grpc/blob/master/src/client/server_streaming.rs#L21-L32 - -```rust -// code written in futures 0.2 style - -impl Future for ResponseFuture -where T: Message + Default, - U: Future>, - B: Body, -{ - type Item = ::Response>; - type Error = ::Error; - - fn poll(&mut self, cx: &mut task::Context) -> Poll { - self.inner.poll(cx) - } -} -``` - -To port this code, we systematically employ a few steps: - -- Implementing `Unpin` for the manual future, which opts out of interior - borrowing. -- Use `TryFuture` for all bounds over futures, and return `PollResult` in `poll`. -- Call `poll_unpin` rather than `poll` when polling inner futures. - -All told, the ported code looks as follows: - -```rust -// now ported to futures 0.3 - -impl Unpin for ResponseFuture {} - -impl Future for ResponseFuture -where T: Message + Default, - U: TryFuture> + Unpin, - B: Body, -{ - type Output = Result<::Response>, ::Error>; - - fn poll(self: PinMut, cx: &mut task::Context) -> PollResult { - self.inner.poll_unpin(cx) - } -} -``` - -These changes are mechanical enough that it's likely possible to write a script -to perform them with high accuracy. - -To be clear, however, there is a definite ergonomic hit here, which is discussed -further in the drawbacks section. - -## Stabilization plan - -The ultimate goal is to ship async/await as part of Rust 2018 (roughly by -mid-September). - -Much of the design has been vetted over quite a long period (futures 0.1), and -the 0.2 changes have gotten substantial use in Google's Fuchsia OS. The task -system in particular has existed in roughly the proposed shape for quite a long -time in the futures crate, and has thus already been quite thoroughly vetted. - -Thus the major new elements are, once more, the removal of built-in errors and -the use of `PinMut`. - -- For built-in errors, the situation is straightforward: we can provide - equivalent functionality at mild ergonomic cost (an extra import). - -- For `PinMut`, there's a clear way to "opt out" and recover the previous - semantics (again at some ergonomic cost), so the primary questions come down - to the `PinMut` API itself. The core types have already received significant - vetting within the RustBelt formal model. In addition to the opt-out, there's - ongoing work in making `PinMut` ergonomic and safe to use directly (rather - than only through `async` notation). - -Tactically, the proposal is to produce a 0.3-beta release, which will be -nightly-only, and to work through support in as large a chunk of the ecosystem -as we can manage, by adding feature flags to crates to opt in to the new -version. - -## Details for `no_std` compatibility - -The APIs proposed above are almost entirely compatible with `core`, except for a -couple of constructors that require `std` objects: - -- Constructing a `Waker` from an `Arc: Wake` -- Constructing a `TaskObj` from a future - -These both have a similar shape: we have a concrete but opaque type (`Waker`, -`TaskObj`) that represents a trait object, but does *not* force a particular -*representation* for the trait object. In `std` environments, you can largely -gloss over this point and just use `Arc` or `Box` respectively. But internally, -the `Waker` and `TaskObj` types are more abstract. - -We'll look at the `Waker` case in detail. The idea is to provide an `UnsafeWake` -trait which represents "an arbitrary `Wake`-like trait object": - -```rust -/// An unsafe trait for implementing custom memory management for a -/// `Waker`. -/// -/// A `Waker` conceptually is a cloneable trait object for `Wake`, and is -/// most often essentially just `Arc: Wake`. However, in some contexts -/// (particularly `no_std`), it's desirable to avoid `Arc` in favor of some -/// custom memory management strategy. This trait is designed to allow for such -/// customization. -/// -/// A default implementation of the `UnsafeWake` trait is provided for the -/// `Arc` type in the standard library. -pub unsafe trait UnsafeWake { - /// Creates a new `Waker` from this instance of `UnsafeWake`. - /// - /// This function will create a new uniquely owned handle that under the - /// hood references the same notification instance. In other words calls - /// to `wake` on the returned handle should be equivalent to calls to - /// `wake` on this handle. - /// - /// # Unsafety - /// - /// This function is unsafe to call because it's asserting the `UnsafeWake` - /// value is in a consistent state, i.e. hasn't been dropped. - unsafe fn clone_raw(self: *mut Self) -> Waker; - - /// Drops this instance of `UnsafeWake`, deallocating resources - /// associated with it. - /// - /// # Unsafety - /// - /// This function is unsafe to call because it's asserting the `UnsafeWake` - /// value is in a consistent state, i.e. hasn't been dropped - unsafe fn drop_raw(self: *mut Self); - - /// Indicates that the associated task is ready to make progress and should - /// be `poll`ed. - /// - /// Executors generally maintain a queue of "ready" tasks; `wake` should place - /// the associated task onto this queue. - /// - /// # Panics - /// - /// Implementations should avoid panicking, but clients should also be prepared - /// for panics. - /// - /// # Unsafety - /// - /// This function is unsafe to call because it's asserting the `UnsafeWake` - /// value is in a consistent state, i.e. hasn't been dropped - unsafe fn wake(self: *mut self); -} -``` - -We then provide the following constructor for `Waker`: - -```rust -impl Waker { - pub unsafe fn new(inner: *const dyn UnsafeWake) -> Waker; -} -``` - -and a `From>` (where `Arc: Wake`) impl that uses it. - -## Task-local storage - -This RFC does not propose any implicit, built-in task-local storage. (Explicit -storage is always possible). - -Task-local storage is implementable on top of the proposed APIs by wrapping a -task in a *scoped* use of *thread*-local storage. When polling the task, a -thread-local value is established and hence usable implicitly within the call -chain. But when returning -- which also happens when the task is blocked -- the -thread-local is moved back out and stored with the task. - -In the future, we anticipate adding "spawn hooks" for the `Context::spawn` -method, essentially allowing you to guarantee that tasks spawned within some -scope are wrapped in some way. That's a separately useful feature, but it can in -particular be used to implement inheritance of task-local data. - -It may be that eventually we do want to build in some task-local data scheme, but: - -- The no_std story is unclear. -- There are a lot of possible designs around things like typemaps and - inheritance, and so it seems best for this work to begin in the ecosystem - first. - -# Rationale, drawbacks, and alternatives - -This RFC is one of the most substantial additions to `std` proposed since -1.0. It commits us to including a particular task and polling model in the -standard library, and ties us to `PinMut`. - -So far we've been able to push the task/polling model into virtually every niche -Rust wishes to occupy, and the main downside has been, in essence, the lack of -async/await syntax (and -the -[borrowing it supports](http://aturon.github.io/2018/04/24/async-borrowing/)). - -This RFC does not attempt to provide a complete introduction to the task model -that originated with the futures crate. A fuller account of the design rationale -and alternatives can be found in the following two blog posts: - -- [Zero-cost futures in Rust](http://aturon.github.io/2016/08/11/futures/) -- [Designing futures for Rust](http://aturon.github.io/2016/09/07/futures-design/) - -To summarize, the main alternative model for futures is a callback-based approach, -which was attempted for several months before the current approach was discovered. -In our experience, the callback approach suffered from several drawbacks in Rust: - -- It forced allocation almost everywhere, and hence was not compatible with no_std. -- It made cancellation *extremely* difficult to get right, whereas with the - proposed model it's just "drop". -- Subjectively, the combinator code was quite hairy, while with the task-based model - things fell into place quickly and easily. - -Some additional context and rationale for the overall async/await project is -available in the [companion RFC]. - -For the remainder of this section, we'll dive into specific API design questions -where this RFC differs from futures 0.2. - -## Rationale, drawbacks and alternatives for removing built-in errors - -There are an assortment of reasons to drop the built-in error type in the main -trait: - -- **Improved type checking and inference**. The error type is one of the biggest - pain points when working with futures combinators today, both in trying to get - different types to match up, and in inference failures that result when a - piece of code cannot produce an error. To be clear, many of these problems - will become less pronounced when `async` syntax is available. - -- **Async functions**. If we retain a built-in error type, it's much less clear - how `async fn` should work: should it always require the return type to be a - `Result`? If not, what happens when a non-`Result` type is returned? - -- **Combinator clarity**. Splitting up the combinators by whether they rely on - errors or not clarifies the semantics. This is *especially* true for streams, - where error handling is a common source of confusion. - -- **Orthogonality**. In general, producing and handling errors is separable from - the core polling mechanism, so all things being equal, it seems good to follow - Rust's general design principles and treat errors by *composing* with `Result`. - -All of that said, there are real downsides for error-heavy code, even with -`TryFuture`: - -- An extra import is needed (obviated if code imports the futures prelude, which - we could perhaps more vocally encourage). - -- It can be confusing for code to *bound* by one trait but *implement* another. - -The error handling piece of this RFC is separable from the other pieces, so the -main alternative would be to retain the built-in error type. - -## Rationale, drawbacks and alternatives to the core trait design (wrt `PinMut`) - -Putting aside error handling, which is orthogonal and discussed above, the -primary other big item in this RFC is the move to `PinMut` for the core polling -method, and how it relates to `Unpin`/manually-written futures. Over the course -of RFC discussions, we've identified essentially three main approaches to this -question: - -- **One core trait**. That's the approach taken in the main RFC text: there's - just a single core `Future` trait, which works on `PinMut`. Separately - there's a `poll_unpin` helper for working with `Unpin` futures in manual - implementations. - -- **Two core traits**. We can provide two traits, for example `MoveFuture` and - `Future`, where one operates on `&mut self` and the other on `PinMut`. - This makes it possible to continue writing code in the futures 0.2 style, - i.e. without importing `PinMut`/`Unpin` or otherwise talking about pins. A - critical requirement is the need for interoperation, so that a `MoveFuture` - can be used anywhere a `Future` is required. There are at least two ways to - achieve such interop: - - - Via a blanket impl of `Future` for `T: MoveFuture`. This approach currently - blocks some *other* desired impls (around `Box` and `&mut` specifically), - but the problem doesn't appear to be fundamental. - - - Via a subtrait relationship, so that `T: Future` is defined essentially as - an alias for `for<'a> PinMut<'a, T>: MoveFuture`. Unfortunately, such - "higher ranked" trait relationships don't currently work well in the trait - system, and this approach also makes things more convoluted when - implementing `Future` by hand, for relatively little gain. - -The drawback of the "one core trait" approach taken by this RFC is its ergonomic -hit when writing moveable futures by hand: you now need to import `PinMut` and -`Unpin`, invoke `poll_unpin`, and impl `Unpin` for your types. This is all -pretty mechanical, but it's a pain. It's possible that improvements in `PinMut` -ergonomics will obviate some of these issues, but there are a lot of open -questions there still. - -On the other hand, a two-trait approach has downsides as well. If we *also* -remove the error type, there's a combinatorial explosion, since we end up -needing `Try` variants of each trait (and this extends to related traits, like -`Stream`, as well). More broadly, with the one-trait approach, `Unpin` acts as a -kind of "independent knob" that can be applied orthogonally from other concerns; -with the two-trait approach, it's "mixed in". And both of the two-trait -approaches run up against compiler limitations at the moment, though of course -that shouldn't be taken as a deciding factor. - -**The primary reason this RFC opts for the one-trait approach is that it's the -conservative, forward-compatible option**. It's possible to add `MoveFuture`, -together with a blanket impl, at any point in the future. Thus, starting with -just the single `Future` trait as proposed in this RFC keeps our options -maximally open while we gain experience. - -## Alternative no_std handling - -Rather than using the `UnsafeWake` trait, we could factor "abstract `Arc`-like -trait objects" out as a first-class concept, `ArcObj`. We would also define an -`ArcLike` trait to determine what concrete types can fit into it: - -```rust -// An `Arc`-like trait object for a trait `T` -// -// Implements `Send`, `Sync` and `Clone` -struct ArcObj { - inner: *mut T, - - // a manually-constructed vtable for the Arc-like methods - drop_fn: unsafe fn(*mut T), - clone_fn: unsafe fn(*mut T) -> ArcObj, -} - -unsafe impl Send for ArcObj {} -unsafe impl Sync for ArcObj {} - -impl Deref for ArcObj { - type Target = T; - fn deref(&self) -> &T { - unsafe { &*self.inner } - } -} - -// An object that can be used like `Arc` -unsafe trait ArcLike: Send + Sync { - fn into_raw(self) -> *mut T; - unsafe fn drop_fn(*mut T); - unsafe fn clone_fn(*mut T) -> ArcObj; -} - -unsafe impl ArcLike for Arc { - fn into_raw(self) -> *mut T { - Arc::into_raw(self) as *mut T - } - - unsafe fn drop_fn(t: *mut T) { - drop(Arc::from_raw(t)); - } - - unsafe fn clone_fn(t: *mut T) -> ArcObj { - let val: Arc = Arc::from_raw(t); - let cloned = val.clone(); - mem::forget(val); - ArcObj::new(cloned) - } -} - -impl ArcObj { - fn new>(u: U) -> ArcObj { - ArcObj { - inner: u.into_raw(), - drop_fn: U::drop_fn, - clone_fn: U::clone_fn, - } - } -} - -impl Clone for ArcObj { - fn clone(&self) -> ArcObj { - unsafe { - (self.clone_fn)(self.inner) - } - } -} - -impl Drop for ArcObj { - fn drop(&mut self) { - unsafe { - (self.drop_fn)(self.inner) - } - } -} -``` - -With this setup, we can define `Waker` as: - -```rust -struct Waker { - obj: ArcObj, -} -``` - -and allow construction from *any* `ArcObj`, rather than just -`Arc`, without using `UnsafeWake`. - -However, this would involve `ArcObj` appearing in multiple places throughout the -API, rather than sequestering the niche case into just the `UnsafeWake` trait as -this RFC proposes. - -# Prior art -[prior-art]: #prior-art - -There is substantial prior art both with async/await notation and with futures -(aka promises) as a basis. The proposed futures API was influenced by Scala's -futures in particular, and is broadly similar to APIs in a variety of other -languages (in terms of the adapters provided). - -What's more unique about the model in this RFC is the use of tasks, rather than -callbacks. The RFC author is not aware of other *futures* libraries using this -technique, but it is a fairly well-known technique more generally in functional -programming. For a recent example, -see -[this paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2011/01/monad-par.pdf) on -parallelism in Haskell. What seems to be perhaps new with this RFC is the idea -of melding the "trampoline" technique with an explicit, open-ended task/wakeup -model. - -# Unresolved questions -[unresolved]: #unresolved-questions - -None at the moment. diff --git a/text/0000-futures.md b/text/0000-futures.md new file mode 100644 index 00000000000..451fd05bef2 --- /dev/null +++ b/text/0000-futures.md @@ -0,0 +1,497 @@ +- Feature Name: (fill me in with a unique ident, my_awesome_feature) +- Start Date: 2018-11-09 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +This RFC proposes to stabilize the library component for the [first-class `async`/`await` +syntax][companion RFC]. In particular, it would stabilize: + +- All APIs of the `std`-level task system, i.e. `std::task::*`. +- The core `Future` API, i.e. `core::future::Future` and `std::future::Future`. + +It does *not* propose to stabilize any of the `async`/`await` syntax itself, which will be proposed in a separate step. It also does not cover stabilization of the `Pin` APIs, which has [already been proposed elsewhere](https://github.com/rust-lang/rust/issues/55766). + +This is a revised and slimmed down version of the [earlier futures RFC](https://github.com/rust-lang/rfcs/pull/2418), which was postponed until more experience was gained on nightly. + +[pin]: https://github.com/rust-lang/rfcs/pull/2349 +[companion RFC]: https://github.com/rust-lang/rfcs/pull/2394 + +# Motivation +[motivation]: #motivation + +## Why `Future`s in `std`? + +The core motivation for this RFC is to stabilize the supporting mechanisms for +`async`/`await` syntax. The syntax itself is motivated in the (already merged) +[companion RFC], and there is a [blog post](http://aturon.github.io/2018/04/24/async-borrowing/) +that goes through its importance in greater detail. + +As with closures, `async` syntax involves producing an anonymous type that implements +a key trait: `Future`. Because `async`/`await` requires language-level support, +the underlying trait must also be part of the standard library. Thus, the goal +of this RFC is to stabilize this this `Future` trait and the types it depends on. +This is the last step needed before we are in a position to stabilize `async`/`await` +itself. + +## How does this step fit into the bigger picture? + +The `async`/`await` syntax is one of the most eagerly desired features in Rust, and +will have a major impact on the ecosystem. It, and the APIs described here, have been +available on nightly and put into major use since late May 2018. + +Stabilizing the futures API portion of this design makes it easier for libraries to +both work on stable Rust *and* to seamlessly support use of `async`/`await` on nightly. +It also allows us to finalize design debate on the API portion, and focus on the few +remaining questions about `async` syntax before it, too, is stabilized. + +# Historical context + +The APIs proposed for stabilization have a lengthy history: + +- The `Future` trait began with the futures crate; [0.1 was released](http://aturon.github.io/2016/08/11/futures/) +in August of 2016. That release established the core ideas of the task/polling model, +as well as many other aspects of the API that are retained here. The 0.1 series +continues to be heavily used throughout the Rust ecosystem and in production systems. + +- In early 2018, as work began toward `async`/`await`, the futures team set up +an RFC process and wrote [several RFCs](https://github.com/rust-lang-nursery/futures-rfcs/pulls?q=is%3Apr+is%3Aclosed) to make revisions to the core APIs based +on longstanding community feedback. These RFCs ultimately resulted in a [0.2le release](http://aturon.github.io/2018/02/27/futures-0-2-RC/), which [shipped](http://aturon.github.io/2018/04/06/futures2/) in April. + +- During the same period, @withoutboats's work on the pinning APIs supporting borrowing +within `async` blocks [came to completion](https://boats.gitlab.io/blog/post/2018-04-06-async-await-final/). +The [pinning APIs](https://github.com/rust-lang/rfcs/pull/2349) were a game-changer, making it possible to support borrowing-across-yield *without* making the core future APIs unsafe. + +- In April 2018, a pair of RFCs formally proposed the `async`/`await` syntax as well as further revision of the futures API (to take advantage of the pinning APIs); the latter went through many revisions, including a [fresh RFC](https://github.com/rust-lang/rfcs/pull/2418). Ultimately, the [syntax RFC was merged](https://github.com/rust-lang/rfcs/pull/2394#issuecomment-387550523) in May, while the API RFC was closed, with [the understanding](https://github.com/rust-lang/rfcs/pull/2418#issuecomment-415841459) that further design iteration would occur on nightly, to be followed up by a stabilization RFC: this one! + +- The APIs [landed in `std`](https://github.com/rust-lang/rust/pull/51263) at the end of May. + +- Since then, the syntax, the `std` APIs, and the futures 0.3 crate have all evolved in tandem as we've gained experience with the APIs. A major driver in this experience has been Google's Fuchsia project, which is using *all* of these features at large scale in an operating system setting. + +- The most recent revisions were in August, and involved [some insights](https://boats.gitlab.io/blog/post/rethinking-pin/) into how to make the `Pin` APIs even cleaner. These APIs have been [proposed for stabilization](https://github.com/rust-lang/rust/issues/55766), as has [their use as `self` types](https://github.com/rust-lang/rust/issues/55786). + +- There are multiple compatibility layers available for using futures 0.1 and 0.3 simultaneously. That's important, because it allows for *incremental* migration of existing production code. + +Since the initial futures 0.3 release, relatively little has changed about the core `Future` trait and task system, other than the refinements mentioned above. The actual `Future` trait has stayed essentially as it was back in April. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +The `Future` trait represents an *asynchronous* and lazy computation that may +eventually produce a final value, but doesn't have to block the current thread +to do so. + +Futures can be constructed through `async` blocks or `async` functions, e.g., + +```rust +async fn read_frame(socket: &TcpStream) -> Result { ... } +``` + +This `async` function, when invoked, produces a future that represents the +completion of reading a frame from the given socket. The function signature +is equivalent to: + +```rust +fn read_frame<'sock>(socket: &'sock TcpStream) + -> impl Future> + 'sock; +``` + +Other async functions can *await* this future; see the [companion +RFC] for full details. + +In addition to `async fn` definitions, futures can be built using adapters, much +like with `Iterator`s. Initially these adapters will be provided entirely "out +of tree", but eventually they will make their way into the standard library. + +Ultimately asynchronous computations are executed by *tasks*, which are +lightweight threads. In particular, an *executor* is able to "spawn" a +`()`-producing `Future` as an independent task; these tasks are then +cooperatively scheduled onto one or more operating system threads. The +`Executor` trait defines this interface, and the `task` module provides a host +of related definitions needed when manually implementing `Future`s or +executors. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## `core::task` module + +The fundamental mechanism for asynchronous computation in Rust is *tasks*, which +are lightweight threads of execution; many tasks can be cooperatively scheduled +onto a single operating system thread. + +To perform this cooperative scheduling we use a technique sometimes referred to +as a "trampoline". When a task would otherwise need to block waiting for some +event, instead it schedules itself for later wakeup and *returns* to the +executor running it, which can then run another task. Subsequent wakeups place +the task back on the executors queue of ready tasks, much like a thread +scheduler in an operating system. + +Attempting to complete a task (or async value within it) is called *polling*, +and always yields a `Poll` value back: + +```rust +/// Indicates whether a value is available, or if the current task has been +/// scheduled for later wake-up instead. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Poll { + /// Represents that a value is immediately ready. + Ready(T), + + /// Represents that a value is not ready yet. + /// + /// When a function returns `Pending`, the function *must* also + /// ensure that the current task is scheduled to be awoken when + /// progress can be made. + Pending, +} +``` + +When a task returns `Poll::Ready`, the executor knows the task has completed and +can be dropped. + +### Waking up + +Each task executor provides its own scheduling facilities, and hence needs to +customize the way task wakeups are handled. Most of the time, you should use the +`std::task::Wake` trait defining wakeup behavior: + +```rust +/// A way of waking up a specific task. +/// +/// Any task executor must provide a way of signaling that a task it owns +/// is ready to be `poll`ed again. Executors do so by providing a wakeup handle +/// type that implements this trait. +pub trait Wake: Send + Sync { + /// Indicates that the associated task is ready to make progress and should + /// be `poll`ed. + /// + /// Executors generally maintain a queue of "ready" tasks; `wake` should place + /// the associated task onto this queue. + fn wake(self: &Arc); + + /// Indicates that the associated task is ready to make progress and should be polled. + /// This function is like wake, but can only be called from the thread on which this + /// `Wake` was created. + /// + /// Executors generally maintain a queue of "ready" tasks; `wake_local` should place + /// the associated task onto this queue. + + unsafe fn wake_local(self: &Arc) +} +``` + +The use of `&Arc` rather than just `&self` makes it possible to work directly with +the trait object for `Wake`, including cloning it. With `UnsafeWake` below, we'll see +an API with greater flexibility for the cases where `Arc` is problematic. + +In general async values are not coupled to any particular executor, so we use trait +objects to handle waking. These come in two forms: `Waker` for the general case, and +`LocalWaker` to provide more effenciency when the wakeup is guaranteed to occur on the +executor thread: + +```rust +/// A `Waker` is a handle for waking up a task by notifying its executor that it +/// is ready to be run. +/// +/// This handle contains a trait object pointing to an instance of the `UnsafeWake` +/// trait, allowing notifications to get routed through it. +/// +/// Implements `Clone`, `Send`, and `Sync`. +pub struct Waker { ... } + +impl Waker { + /// Wake up the task associated with this `Waker`. + pub fn wake(&self); +} + +/// A `LocalWaker` is a handle for waking up a task by notifying its executor that it is ready to be run. + +/// This is similar to the `Waker` type, but cannot be sent across threads. Task executors can use this type to implement more optimized singlethreaded wakeup behavior. + +impl LocalWaker { + /// Wake up the task associated with this `LocalWaker`. + pub fn wake(&self); +} + +impl From for Waker { .. } +``` + +Task execution always happens in the context of a `LocalWaker` that can be used to +wake the task up locally, or converted into a `Waker` that can be sent to other threads. + +It's possible to construct a `Waker` using `From>`. + +### `UnsafeWake` and `no_std` compatibility + +The [`UnsafeWake` trait](https://doc.rust-lang.org/nightly/std/task/trait.UnsafeWake.html) +in `core::task` is designed to support task wakeup in `no_std` environments, where +we cannot use `Arc`. It is *not* proposed for stabilization at this time, because +its APIs are awaiting revision based on object safety for `*mut self` methods. + +## `core::future` module + +With all of the above task infrastructure in place, defining `Future` is +straightforward: + +```rust +pub trait Future { + /// The type of value produced on completion. + type Output; + + /// Attempt to resolve the future to a final value, registering + /// the current task for wakeup if the value is not yet available. + /// + /// # Return value + /// + /// This function returns: + /// + /// - [`Poll::Pending`] if the future is not ready yet + /// - [`Poll::Ready(val)`] with the result `val` of this future if it + /// finished successfully. + /// + /// Once a future has finished, clients should not `poll` it again. + /// + /// When a future is not ready yet, `poll` returns `Poll::Pending` and + /// stores a clone of the [`LocalWaker`] to be woken once the future can + /// make progress. For example, a future waiting for a socket to become + /// readable would call `.clone()` on the [`LocalWaker`] and store it. + /// When a signal arrives elsewhere indicating that the socket is readable, + /// `[LocalWaker::wake]` is called and the socket future's task is awoken. + /// Once a task has been woken up, it should attempt to `poll` the future + /// again, which may or may not produce a final value. + /// + /// Note that on multiple calls to `poll`, only the most recent + /// [`LocalWaker`] passed to `poll` should be scheduled to receive a + /// wakeup. + /// + /// # Runtime characteristics + /// + /// Futures alone are *inert*; they must be *actively* `poll`ed to make + /// progress, meaning that each time the current task is woken up, it should + /// actively re-`poll` pending futures that it still has an interest in. + /// + /// The `poll` function is not called repeatedly in a tight loop-- instead, + /// it should only be called when the future indicates that it is ready to + /// make progress (by calling `wake()`). If you're familiar with the + /// `poll(2)` or `select(2)` syscalls on Unix it's worth noting that futures + /// typically do *not* suffer the same problems of "all wakeups must poll + /// all events"; they are more like `epoll(4)`. + /// + /// An implementation of `poll` should strive to return quickly, and must + /// *never* block. Returning quickly prevents unnecessarily clogging up + /// threads or event loops. If it is known ahead of time that a call to + /// `poll` may end up taking awhile, the work should be offloaded to a + /// thread pool (or something similar) to ensure that `poll` can return + /// quickly. + /// + /// # [`LocalWaker`], [`Waker`] and thread-safety + /// + /// The `poll` function takes a [`LocalWaker`], an object which knows how to + /// awaken the current task. [`LocalWaker`] is not `Send` nor `Sync`, so in + /// order to make thread-safe futures the [`LocalWaker::into_waker`] method + /// should be used to convert the [`LocalWaker`] into a thread-safe version. + /// [`LocalWaker::wake`] implementations have the ability to be more + /// efficient, however, so when thread safety is not necessary, + /// [`LocalWaker`] should be preferred. + /// + /// # Panics + /// + /// Once a future has completed (returned `Ready` from `poll`), + /// then any future calls to `poll` may panic, block forever, or otherwise + /// cause bad behavior. The `Future` trait itself provides no guarantees + /// about the behavior of `poll` after a future has completed. + /// + /// [`Poll::Pending`]: ../task/enum.Poll.html#variant.Pending + /// [`Poll::Ready(val)`]: ../task/enum.Poll.html#variant.Ready + /// [`LocalWaker`]: ../task/struct.LocalWaker.html + /// [`LocalWaker::into_waker`]: ../task/struct.LocalWaker.html#method.into_waker + /// [`LocalWaker::wake`]: ../task/struct.LocalWaker.html#method.wake + /// [`Waker`]: ../task/struct.Waker.html + fn poll(self: Pin<&mut Self>, lw: &LocalWaker) -> Poll; +} +``` + +Most of the explanation here follows what we've already said about the task +system. The one twist is the use of `Pin`, which makes it possible to keep data +borrowed across separate calls to `poll` (i.e., "borrowing over yield +points"). The mechanics of pinning are explained +in [the RFC that introduced it](https://github.com/rust-lang/rfcs/pull/2349) +and the [blog post about t he latest revisions](https://boats.gitlab.io/blog/post/rethinking-pin/). + +## Relation to futures 0.1 + +The various discussions outlined in the historical context section above cover the +path to these APIs from futures 0.1. But, in a nutshell, there are three major shifts: + +- The use of `Pin<&mut self>` rather than just `&mut self`, which is necessary +to support borrowing withing `async` blocks. The `Unpin` marker trait can be used +to restore ergonomics and safety similar to futures 0.1 when writing futures by hand. + +- Dropping *built in* errors from `Future`, in favor of futures returning a `Result` +when they can fail. The futures 0.3 crate provides a `TryFuture` trait that bakes +in the `Result` to provide better ergonomics when working with `Result`-producing futures. +Dropping the error type has been discussed in previous threads, but the most +important rationale is to provide an orthogonal, compositional semantics for `async fn` +that mirrors normal `fn`, rather than *also* baking in a particular style of +error handling. + +- Passing a `LocalWaker` explicitly, rather than stashing it in thread-local storage. +This has been a hotly debated issue since futures 0.1 was released, and this +RFC does not seek to relitigate it, but to summarize, the major advantages are (1) +when working with manual futures (as opposed to `async` blocks) it's much easier to +tell where an ambient task is required, and (2) `no_std` compatibility is +significantly smoother. + +To bridge the gap between futures 0.1 and 0.3, there are several compatibility shims, +including one built into the futures crate itself, where you can shift between the two +simply by using a `.compat()` combinator. These compatibility layers make it possible +to use the existing ecosystem smoothly with the new futures APIs, and make it possible +to transition large code bases incrementally. + +# Rationale, drawbacks, and alternatives + +This RFC is one of the most substantial additions to `std` proposed since +1.0. It commits us to including a particular task and polling model in the +standard library, and ties us to `Pin`. + +So far we've been able to push the task/polling model into virtually every niche +Rust wishes to occupy, and the main downside has been, in essence, the lack of +async/await syntax (and +the +[borrowing it supports](http://aturon.github.io/2018/04/24/async-borrowing/)). + +This RFC does not attempt to provide a complete introduction to the task model +that originated with the futures crate. A fuller account of the design rationale +and alternatives can be found in the following two blog posts: + +- [Zero-cost futures in Rust](http://aturon.github.io/2016/08/11/futures/) +- [Designing futures for Rust](http://aturon.github.io/2016/09/07/futures-design/) + +To summarize, the main alternative model for futures is a callback-based approach, +which was attempted for several months before the current approach was discovered. +In our experience, the callback approach suffered from several drawbacks in Rust: + +- It forced allocation almost everywhere, and hence was not compatible with no_std. +- It made cancellation *extremely* difficult to get right, whereas with the + proposed model it's just "drop". +- Subjectively, the combinator code was quite hairy, while with the task-based model + things fell into place quickly and easily. + +Some additional context and rationale for the overall async/await project is +available in the [companion RFC]. + +For the remainder of this section, we'll dive into specific API design questions +where this RFC differs from futures 0.2. + +## Rationale, drawbacks and alternatives for removing built-in errors + +There are an assortment of reasons to drop the built-in error type in the main +trait: + +- **Improved type checking and inference**. The error type is one of the biggest + pain points when working with futures combinators today, both in trying to get + different types to match up, and in inference failures that result when a + piece of code cannot produce an error. To be clear, many of these problems + will become less pronounced when `async` syntax is available. + +- **Async functions**. If we retain a built-in error type, it's much less clear + how `async fn` should work: should it always require the return type to be a + `Result`? If not, what happens when a non-`Result` type is returned? + +- **Combinator clarity**. Splitting up the combinators by whether they rely on + errors or not clarifies the semantics. This is *especially* true for streams, + where error handling is a common source of confusion. + +- **Orthogonality**. In general, producing and handling errors is separable from + the core polling mechanism, so all things being equal, it seems good to follow + Rust's general design principles and treat errors by *composing* with `Result`. + +All of that said, there are real downsides for error-heavy code, even with +`TryFuture`: + +- An extra import is needed (obviated if code imports the futures prelude, which + we could perhaps more vocally encourage). + +- It can be confusing for code to *bound* by one trait but *implement* another. + +The error handling piece of this RFC is separable from the other pieces, so the +main alternative would be to retain the built-in error type. + +## Rationale, drawbacks and alternatives to the core trait design (wrt `Pi`) + +Putting aside error handling, which is orthogonal and discussed above, the +primary other big item in this RFC is the move to `Pin` for the core polling +method, and how it relates to `Unpin`/manually-written futures. Over the course +of RFC discussions, we've identified essentially three main approaches to this +question: + +- **One core trait**. That's the approach taken in the main RFC text: there's + just a single core `Future` trait, which works on `Pin<&mut Self>`. Separately + there's a `poll_unpin` helper for working with `Unpin` futures in manual + implementations. + +- **Two core traits**. We can provide two traits, for example `MoveFuture` and + `Future`, where one operates on `&mut self` and the other on `Pin<&mut Self>`. + This makes it possible to continue writing code in the futures 0.2 style, + i.e. without importing `Pin`/`Unpin` or otherwise talking about pins. A + critical requirement is the need for interoperation, so that a `MoveFuture` + can be used anywhere a `Future` is required. There are at least two ways to + achieve such interop: + + - Via a blanket impl of `Future` for `T: MoveFuture`. This approach currently + blocks some *other* desired impls (around `Box` and `&mut` specifically), + but the problem doesn't appear to be fundamental. + + - Via a subtrait relationship, so that `T: Future` is defined essentially as + an alias for `for<'a> Pin<&mut 'a T>: MoveFuture`. Unfortunately, such + "higher ranked" trait relationships don't currently work well in the trait + system, and this approach also makes things more convoluted when + implementing `Future` by hand, for relatively little gain. + +The drawback of the "one core trait" approach taken by this RFC is its ergonomic +hit when writing moveable futures by hand: you now need to import `Pin` and +`Unpin`, invoke `poll_unpin`, and impl `Unpin` for your types. This is all +pretty mechanical, but it's a pain. It's possible that improvements in `Pin` +ergonomics will obviate some of these issues, but there are a lot of open +questions there still. + +On the other hand, a two-trait approach has downsides as well. If we *also* +remove the error type, there's a combinatorial explosion, since we end up +needing `Try` variants of each trait (and this extends to related traits, like +`Stream`, as well). More broadly, with the one-trait approach, `Unpin` acts as a +kind of "independent knob" that can be applied orthogonally from other concerns; +with the two-trait approach, it's "mixed in". And both of the two-trait +approaches run up against compiler limitations at the moment, though of course +that shouldn't be taken as a deciding factor. + +**The primary reason this RFC opts for the one-trait approach is that it's the +conservative, forward-compatible option, and has proven itself in practice**. +It's possible to add `MoveFuture`, together with a blanket impl, at any point in the future. +Thus, starting with just the single `Future` trait as proposed in this RFC keeps our options +maximally open while we gain experience. + +# Prior art +[prior-art]: #prior-art + +There is substantial prior art both with async/await notation and with futures +(aka promises) as a basis. The proposed futures API was influenced by Scala's +futures in particular, and is broadly similar to APIs in a variety of other +languages (in terms of the adapters provided). + +What's more unique about the model in this RFC is the use of tasks, rather than +callbacks. The RFC author is not aware of other *futures* libraries using this +technique, but it is a fairly well-known technique more generally in functional +programming. For a recent example, +see +[this paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2011/01/monad-par.pdf) on +parallelism in Haskell. What seems to be perhaps new with this RFC is the idea +of melding the "trampoline" technique with an explicit, open-ended task/wakeup +model. + +# Unresolved questions +[unresolved]: #unresolved-questions + +None at the moment. From bb242c8cc6ee2f873e6f95d1bf02cedf4cded468 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 9 Nov 2018 15:35:07 -0800 Subject: [PATCH 04/22] Address nits --- text/0000-futures.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index 451fd05bef2..6279f6660cf 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -178,11 +178,33 @@ pub trait Wake: Send + Sync { /// /// Executors generally maintain a queue of "ready" tasks; `wake_local` should place /// the associated task onto this queue. - unsafe fn wake_local(self: &Arc) } ``` +To see how this might be used in practice, here's a simple example sketch: + +```rust +struct ExecutorInner { + sync_ready_queue: SynchronousQueue, + optimized_queue: UnsafeCell>, +} + +struct Task { + future: ..., + executor: Arc, +} + +impl Wake for Task { + fn wake(self: &Arc) { + self.executor.sync_ready_queue.push(self.clone()); + } + unsafe fn wake_local(self: &Arc) { + (&mut *self.executor.optimized_queue.get()).push(self.clone()) + } +} +``` + The use of `&Arc` rather than just `&self` makes it possible to work directly with the trait object for `Wake`, including cloning it. With `UnsafeWake` below, we'll see an API with greater flexibility for the cases where `Arc` is problematic. @@ -208,14 +230,15 @@ impl Waker { } /// A `LocalWaker` is a handle for waking up a task by notifying its executor that it is ready to be run. - +/// /// This is similar to the `Waker` type, but cannot be sent across threads. Task executors can use this type to implement more optimized singlethreaded wakeup behavior. - impl LocalWaker { /// Wake up the task associated with this `LocalWaker`. pub fn wake(&self); } +/// You can upgrade to a sendable `Waker` at zero cost, but waking through a `Waker` is more expensive +/// due to synchronization. impl From for Waker { .. } ``` From 1c8961e6e61a94b15cbb06a3b62e598eff466d08 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Sat, 10 Nov 2018 08:42:09 -0800 Subject: [PATCH 05/22] Typo --- text/0000-futures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index 6279f6660cf..cdf694e1b99 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -443,7 +443,7 @@ All of that said, there are real downsides for error-heavy code, even with The error handling piece of this RFC is separable from the other pieces, so the main alternative would be to retain the built-in error type. -## Rationale, drawbacks and alternatives to the core trait design (wrt `Pi`) +## Rationale, drawbacks and alternatives to the core trait design (wrt `Pin`) Putting aside error handling, which is orthogonal and discussed above, the primary other big item in this RFC is the move to `Pin` for the core polling From 96ba92480ac487441ab3a44213be6432872ba936 Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Sat, 17 Nov 2018 22:20:19 -0800 Subject: [PATCH 06/22] Move the descriptions of LocalWaker and Waker and the primary focus. --- text/0000-futures.md | 342 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 280 insertions(+), 62 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index cdf694e1b99..6bbf90f7ee1 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -105,13 +105,21 @@ In addition to `async fn` definitions, futures can be built using adapters, much like with `Iterator`s. Initially these adapters will be provided entirely "out of tree", but eventually they will make their way into the standard library. -Ultimately asynchronous computations are executed by *tasks*, which are -lightweight threads. In particular, an *executor* is able to "spawn" a -`()`-producing `Future` as an independent task; these tasks are then -cooperatively scheduled onto one or more operating system threads. The -`Executor` trait defines this interface, and the `task` module provides a host -of related definitions needed when manually implementing `Future`s or -executors. +Ultimately asynchronous computations are executed in the form of *tasks*, +which are comparable to lightweight threads. A `task` is a `()`-producing `Future`, +which is owned by an *executor*, and polled to completion while the being pinned. + +*executor*'s provide the ability to "spawn" such `Future`s. +The implementation of an **executor** schedules the `task` it owns in a cooperative +fashion. It is up to the implementation of an **executor** whether on or more +operation system threads are used for this, as well as how many `task`s can be +spawned on them in parallel. + +This RFC does not include any definition of an executor. It merely defines the +interaction between **executor**s, `task`s and `Future`s, in the form of APIs +that allow `task`s to request getting scheduled again. +The `task` module provides these APIs, which are required when manually implementing +`Future`s or **executors**. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -124,10 +132,10 @@ onto a single operating system thread. To perform this cooperative scheduling we use a technique sometimes referred to as a "trampoline". When a task would otherwise need to block waiting for some -event, instead it schedules itself for later wakeup and *returns* to the -executor running it, which can then run another task. Subsequent wakeups place -the task back on the executors queue of ready tasks, much like a thread -scheduler in an operating system. +event, instead it saves an object that allows it to get scheduled again later +and *returns* to the executor running it, which can then run another task. +Subsequent wakeups place the task back on the executors queue of ready tasks, +much like a thread scheduler in an operating system. Attempting to complete a task (or async value within it) is called *polling*, and always yields a `Poll` value back: @@ -154,9 +162,261 @@ can be dropped. ### Waking up -Each task executor provides its own scheduling facilities, and hence needs to -customize the way task wakeups are handled. Most of the time, you should use the -`std::task::Wake` trait defining wakeup behavior: +If a future can not be directly fulfilled during execution and returns `Pending`, +it needs a way to later on inform the executor that it needs to get polled again +to make progress. + +This functionality is provided through a set of `Waker` types. + +`Waker`s are objects which are passed as a parameter to the `Future::poll` call, +and which can be stored by the implementation of those `Futures`s. Whenever a +`Future` has the need to get polled again, it can use the `wake` method of the +waker in order to inform the executor that the `task` which owns the `Future` +should get scheduled and executed again. + +The RFC defines 2 concrete `Waker` types, with which implementors of `Futures` +and asynchronous functions will interact: `Waker` and `LocalWaker`. Both of +these types define a `wake(&self)` function which is used to schedule the `task` +that is associated to the `Waker` again. + +The difference between those types is that `Waker` implements the `Send` and `Sync` +marker traits, while `LocalWaker` doesn't. This means a `Waker` can be sent to +another thread and stored there in order to wake up the associated `task` later on, +while a `LocalWaker` can't be sent. Depending on the capabilities of the underlying +executor a `LocalWaker` can be converted into a `Waker`. Most executors in the +ecosystem will implement this functionality. The exception will be highly +specialized executors, which e.g. want to avoid the cost of all synchronization. + +Calling the `wake()` method on a `Waker` will in general be more expensive than +on a `LocalWaker` instance, due to additional synchronization. + +Executors will always pass a `LocalWaker` instance to the `task`s they poll. + +The mechanism through which `task`s get scheduled again depends on the **executor** +which is driving the `task`. +Possible ways of waking up a an executor include: +- If the **executor** is blocked on a condition variable, the condition variable + needs to get notified. +- If the **executor** is blocked on a system call like `select`, it might need + to get woken up by a syscall like `write` to a pipe. +- If the **executor**'s thread is parked, the wakeup call needs to unpark it. + +In order to accomodate for this behavior, the `Waker` and `LocalWaker` types are +defined through a `RawWaker` type, which is an struct that defines a dynamic +dispatch mechanism, which consists of an raw object pointer and a virtual function +pointer table (vtable). This mechanism allows implementors of an **executor** to +customize the behavior of `RawWaker`, `Waker` and `LocalWaker` objects. + +The relation between those `Waker` types is outlined in the following definitions: + +```rust +/// A `RawWaker` allows the implementor of a task executor to customize the +/// behavior of `LocalWaker`s and `Waker`s. +/// It consists of a data pointer a virtual function pointer table (vtable) that +/// customizes the behavior of the `RawWaker`. +#[derive(PartialEq)] +pub struct RawWaker { + /// A data pointer, which can be used to store arbitrary data as required + /// by the executor. This could be e.g. a type-erased pointer to an `Arc` + /// that is associated with the task. + /// The value of this field gets passed to all functions that are part of + /// the vtable as first parameter. + pub data: *const (), + /// Virtual function pointer table that customizes the behavior if this waker. + pub vtable: &'static RawWakerVTable, +} + +/// A virtual function pointer table (vtable) that allows to customize the +/// behavior of a `RawWaker`. +/// The pointers which get passed to all functions inside the vtable are the +/// values of the `data` field. +#[derive(PartialEq, Copy, Clone)] +pub struct RawWakerVTable { + /// This function will be called when the `RawWaker` gets cloned, e.g. when + /// the `Waker` or `LocalWaker` in which the `RawWaker` is stored gets cloned. + /// The implementation of this function must retain all resources that are + /// required for this additional instance of a `RawWaker` and associated + /// task. + pub clone: unsafe fn(*const ()) -> *const (), + /// This function will be called when a `LocalWaker` should be converted into + /// a thread-safe `Waker`. The implementation of this function must return + /// a new `RawWaker` which can fulfill the requirements of `Waker`. It can + /// exchange the vtable for that purpose. E.g. it might replace the `wake` + /// function with a varient that supports cross-thread wakeups. + /// + /// If a conversion is not supported, the implementation should return + /// `INVALID_RAW_WAKER`. + pub into_waker: unsafe fn(*const ()) -> RawWaker, + /// This function will be called when `wake` is called on the `RawWaker`. + pub wake: unsafe fn(*const ()), + /// This function gets called when a `RawWaker` gets dropped. + /// The implementation of this function must make sure to release any + /// resources that are associated with this instance of a `RawWaker` and + /// associated task. + pub drop_fn: unsafe fn(*const ()), +} + +/// A guard object that can be returned by `into_waker` if the implementation +/// wants to signal that a conversion isn't possible. +pub const INVALID_RAW_WAKER: RawWaker = RawWaker { + data: core::ptr::null(), + vtable: &RawWakerVTable { + clone: invalid_fn_call, // Those panic when called + into_waker: invalid_fn_call, + wake: invalid_fn_call, + drop_fn: invalid_fn_call, + }, +}; + +/// A `Waker` is a handle for waking up a task by notifying its executor that it +/// is ready to be run. +/// +/// This handle encapsulates a `RawWaker` instance, which defines the +/// executor-specific wakeup behavior. +/// +/// Implements `Clone`, `Send`, and `Sync`. +pub struct Waker { + waker: RawWaker, +} + +impl Waker { + /// Wake up the task associated with this `Waker`. + pub fn wake(&self) { + // The actual wakeup call is delegated through a virtual function call + // to the implementation which is defined by the executor + unsafe { (self.waker.vtable.wake)(self.waker.data) } + } + + /// Returns whether or not this Waker and other Waker awaken the same task. + /// + /// This function works on a best-effort basis, and may return false even + /// when the Wakers would awaken the same task. However, if this function + /// returns true, it is guaranteed that the Wakers will awaken the same task. + /// + /// This function is primarily used for optimization purposes. + pub fn will_wake(&self, other: &Waker) -> bool { + self.waker.data == other.waker.data && + self.waker.vtable == other.waker.vtable + } + + /// Creates a new `Waker` from `RawWaker`. + /// + /// The method can not check whether `RawWaker` fulfills the required API + /// contract to make it usable for `Waker` and is therefore unsafe. + pub unsafe fn new_unchecked(waker: RawWaker) -> Waker { + Waker { + waker: waker, + } + } +} + +impl Clone for Waker { + fn clone(&self) -> Self { + Waker { + waker: RawWaker { + data: unsafe { (self.waker.vtable.clone)(self.waker.data) }, + vtable: self.waker.vtable, + } + } + } +} + +impl Drop for Waker { + fn drop(&mut self) { + unsafe { (self.waker.vtable.drop_fn)(self.waker.data) } + } +} + +/// A `LocalWaker` is a handle for waking up a task by notifying its executor that it is ready to be run. +/// +/// This is similar to the `Waker` type, but cannot be sent across threads. +/// Task executors can use this type to implement more optimized singlethreaded wakeup behavior. +/// +/// This handle encapsulates a `RawWaker` instance, which defines the +/// executor-specific wakeup behavior. +/// +/// Implements `Clone` +pub struct LocalWaker { + waker: RawWaker, +} + +impl LocalWaker { + /// Wake up the task associated with this `LocalWaker`. + pub fn wake(&self) { + // The actual wakeup call is delegated through a virtual function call + // to the implementation which is defined by the executor + unsafe { (self.waker.vtable.wake)(self.waker.data) } + } + + /// Converts the `LocalWaker` into `Waker`, which can be sent across + /// thread boundaries. + /// + /// This function can panic if the associated executor does not support + /// getting woken up from a different thread. + pub fn into_waker(&self) -> Waker { + match self.try_into_waker() { + Some(waker) => waker, + None => panic!("Conversion from LocalWaker into Waker is not supported"), + } + } + + /// Tries to convert the `LocalWaker` into a `Waker`, which can be sent + /// across thread boundaries. + /// + /// Returns None if the if the associated executor does not support + /// getting woken up from a different thread. + pub fn try_into_waker(&self) -> Option { + unsafe { + let raw_waker = (self.waker.vtable.into_waker)(self.waker.data); + if raw_waker == INVALID_RAW_WAKER { + return None; + } + Some(Waker::new_unchecked(raw_waker)) + } + } + + /// Returns whether or not this LocalWaker and other LocalWaker awaken the same task. + /// + /// This function works on a best-effort basis, and may return false even + /// when the LocalWakers would awaken the same task. However, if this function + /// returns true, it is guaranteed that the LocalWakers will awaken the same task. + /// + /// This function is primarily used for optimization purposes. + pub fn will_wake(&self, other: &LocalWaker) -> bool { + self.waker.data == other.waker.data && + self.waker.vtable == other.waker.vtable + } + + /// Creates a new `LocalWaker` from `RawWaker`. + /// + /// The method can not check whether `RawWaker` fulfills the required API + /// contract to make it usable for `LocalWaker` and is therefore unsafe. + pub unsafe fn new_unchecked(waker: RawWaker) -> LocalWaker { + LocalWaker { + waker: waker, + } + } +} + +// The implementations of `Clone` and `Drop` follow what is shown in for `Waker`. + +// TODO: Should `Waker` still implement `From` or potentially `TryFrom`? +``` + +`Waker`s must fulfill the following requirements: +- They must be cloneable +- If all instances of a `Waker` have been dropped and their associated `task` had + been driven to completion, all resources which had been allocated for the `task` + must have been released. +- It must be safe to call `wake()` on a `Waker` even if the associated task has + already been driven to completion. +- `Waker::wake()` must wake up an executor even if it is called from an arbitry + thread. + +An executor which implements `RawWaker` must therefore make sure that all these +requirements are fulfilled. + +// TODO: Work on ArcWake ```rust /// A way of waking up a specific task. @@ -164,7 +424,7 @@ customize the way task wakeups are handled. Most of the time, you should use the /// Any task executor must provide a way of signaling that a task it owns /// is ready to be `poll`ed again. Executors do so by providing a wakeup handle /// type that implements this trait. -pub trait Wake: Send + Sync { +pub trait ArcWake: Send + Sync { /// Indicates that the associated task is ready to make progress and should /// be `poll`ed. /// @@ -178,7 +438,7 @@ pub trait Wake: Send + Sync { /// /// Executors generally maintain a queue of "ready" tasks; `wake_local` should place /// the associated task onto this queue. - unsafe fn wake_local(self: &Arc) + fn wake_local(self: &Arc) } ``` @@ -195,7 +455,7 @@ struct Task { executor: Arc, } -impl Wake for Task { +impl ArcWake for Task { fn wake(self: &Arc) { self.executor.sync_ready_queue.push(self.clone()); } @@ -209,51 +469,8 @@ The use of `&Arc` rather than just `&self` makes it possible to work direc the trait object for `Wake`, including cloning it. With `UnsafeWake` below, we'll see an API with greater flexibility for the cases where `Arc` is problematic. -In general async values are not coupled to any particular executor, so we use trait -objects to handle waking. These come in two forms: `Waker` for the general case, and -`LocalWaker` to provide more effenciency when the wakeup is guaranteed to occur on the -executor thread: - -```rust -/// A `Waker` is a handle for waking up a task by notifying its executor that it -/// is ready to be run. -/// -/// This handle contains a trait object pointing to an instance of the `UnsafeWake` -/// trait, allowing notifications to get routed through it. -/// -/// Implements `Clone`, `Send`, and `Sync`. -pub struct Waker { ... } - -impl Waker { - /// Wake up the task associated with this `Waker`. - pub fn wake(&self); -} - -/// A `LocalWaker` is a handle for waking up a task by notifying its executor that it is ready to be run. -/// -/// This is similar to the `Waker` type, but cannot be sent across threads. Task executors can use this type to implement more optimized singlethreaded wakeup behavior. -impl LocalWaker { - /// Wake up the task associated with this `LocalWaker`. - pub fn wake(&self); -} - -/// You can upgrade to a sendable `Waker` at zero cost, but waking through a `Waker` is more expensive -/// due to synchronization. -impl From for Waker { .. } -``` - -Task execution always happens in the context of a `LocalWaker` that can be used to -wake the task up locally, or converted into a `Waker` that can be sent to other threads. - It's possible to construct a `Waker` using `From>`. -### `UnsafeWake` and `no_std` compatibility - -The [`UnsafeWake` trait](https://doc.rust-lang.org/nightly/std/task/trait.UnsafeWake.html) -in `core::task` is designed to support task wakeup in `no_std` environments, where -we cannot use `Arc`. It is *not* proposed for stabilization at this time, because -its APIs are awaiting revision based on object safety for `*mut self` methods. - ## `core::future` module With all of the above task infrastructure in place, defining `Future` is @@ -314,8 +531,9 @@ pub trait Future { /// /// The `poll` function takes a [`LocalWaker`], an object which knows how to /// awaken the current task. [`LocalWaker`] is not `Send` nor `Sync`, so in - /// order to make thread-safe futures the [`LocalWaker::into_waker`] method - /// should be used to convert the [`LocalWaker`] into a thread-safe version. + /// order to make thread-safe futures the [`LocalWaker::into_waker`] and + /// [`LocalWaker::try_into_waker`] methods should be used to convert + /// the [`LocalWaker`] into a thread-safe version. /// [`LocalWaker::wake`] implementations have the ability to be more /// efficient, however, so when thread safety is not necessary, /// [`LocalWaker`] should be preferred. From 2ff9713922d9358316f37769853b8d80eb34a371 Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Sat, 17 Nov 2018 22:49:16 -0800 Subject: [PATCH 07/22] Add a comment why RawWaker is used --- text/0000-futures.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/text/0000-futures.md b/text/0000-futures.md index 6bbf90f7ee1..39a504b029a 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -207,6 +207,11 @@ dispatch mechanism, which consists of an raw object pointer and a virtual functi pointer table (vtable). This mechanism allows implementors of an **executor** to customize the behavior of `RawWaker`, `Waker` and `LocalWaker` objects. +This mechanism is chosen in favor of trait objects since it allows for more +flexible memory management schemes. `RawWaker` can be implemented purely in +terms of global functions and state, on top of reference counted objects, or +in other ways. + The relation between those `Waker` types is outlined in the following definitions: ```rust From e40dab4b45f100e35d2dd5adf59142d56cb08400 Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Sun, 18 Nov 2018 01:06:12 -0800 Subject: [PATCH 08/22] Update the section about ArcWake --- text/0000-futures.md | 52 +++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index 39a504b029a..600fc03a0a5 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -300,8 +300,7 @@ impl Waker { /// /// This function is primarily used for optimization purposes. pub fn will_wake(&self, other: &Waker) -> bool { - self.waker.data == other.waker.data && - self.waker.vtable == other.waker.vtable + self.waker == other.waker } /// Creates a new `Waker` from `RawWaker`. @@ -388,8 +387,7 @@ impl LocalWaker { /// /// This function is primarily used for optimization purposes. pub fn will_wake(&self, other: &LocalWaker) -> bool { - self.waker.data == other.waker.data && - self.waker.vtable == other.waker.vtable + self.waker == other.waker } /// Creates a new `LocalWaker` from `RawWaker`. @@ -421,29 +419,53 @@ impl LocalWaker { An executor which implements `RawWaker` must therefore make sure that all these requirements are fulfilled. -// TODO: Work on ArcWake +Since many the ownership semantics that are required here can easily be met +through a reference-counted `Waker` implementation, a convienence method for +defining `Waker`s is provided, which does not require implementing a `RawWaker` +and the associated vtable manually. + +This convience method is based around the `ArcWake` trait. An implementor of +an executor can define a type which implements the `ArcWake` trait as defined +below. The `ArcWake` type defines the associated method, which allows to retrieve +a `LocalWaker` instance from an `Arc` of this type. +The returned instance will guarantee that the `wake()` and `wake_local` methods +of the type which implements `ArcWake` are called, whenever `wake()` is called +on a `Waker` or `LocalWaker`. ```rust /// A way of waking up a specific task. /// -/// Any task executor must provide a way of signaling that a task it owns -/// is ready to be `poll`ed again. Executors do so by providing a wakeup handle -/// type that implements this trait. +/// By implementing this trait, types that are expected to be wrapped in an `Arc` +/// can be converted into `LocalWaker` and `Waker` objects. +/// Those Wakers can be used to signal executors that a task it owns +/// is ready to be `poll`ed again. pub trait ArcWake: Send + Sync { /// Indicates that the associated task is ready to make progress and should /// be `poll`ed. /// + /// This function can called from the thread on which the `ArcWake` was created, + /// as well as from any other thread. + /// /// Executors generally maintain a queue of "ready" tasks; `wake` should place /// the associated task onto this queue. fn wake(self: &Arc); /// Indicates that the associated task is ready to make progress and should be polled. - /// This function is like wake, but can only be called from the thread on which this - /// `Wake` was created. + /// This function is like wake, but will only be called from the thread on which this + /// `ArcWake` was created. /// /// Executors generally maintain a queue of "ready" tasks; `wake_local` should place /// the associated task onto this queue. - fn wake_local(self: &Arc) + fn wake_local(self: &Arc); + + /// Creates a `LocalWaker` from an Arc, if T implements ArcWake. + /// + /// The returned `LocalWaker` will call `wake.wake_local()` when awoken. + /// + /// The returned `LocalWaker` can be converted into a `Waker` through + /// it's `into_waker()` method. If `wake()` is called on this `Waker`, + /// the `wake()` function that is defined inside this trait will get called. + fn into_local_waker(wake: Arc) -> LocalWaker where Self: Sized; } ``` @@ -464,17 +486,17 @@ impl ArcWake for Task { fn wake(self: &Arc) { self.executor.sync_ready_queue.push(self.clone()); } - unsafe fn wake_local(self: &Arc) { + + fn wake_local(self: &Arc) { (&mut *self.executor.optimized_queue.get()).push(self.clone()) } } ``` The use of `&Arc` rather than just `&self` makes it possible to work directly with -the trait object for `Wake`, including cloning it. With `UnsafeWake` below, we'll see -an API with greater flexibility for the cases where `Arc` is problematic. +the trait object for `Wake`, including cloning it. -It's possible to construct a `Waker` using `From>`. +It's possible to construct a `Waker` using `From>`. // TODO: Is that really the case or only `impl Wake`? ## `core::future` module From 405361aa1c689caa7a644bcd848055829258314e Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Sun, 18 Nov 2018 01:08:56 -0800 Subject: [PATCH 09/22] fix sentence --- text/0000-futures.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index 600fc03a0a5..4bf01dfa9b5 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -426,8 +426,8 @@ and the associated vtable manually. This convience method is based around the `ArcWake` trait. An implementor of an executor can define a type which implements the `ArcWake` trait as defined -below. The `ArcWake` type defines the associated method, which allows to retrieve -a `LocalWaker` instance from an `Arc` of this type. +below. The `ArcWake` type defines the associated method `into_local_waker`, +which allows to retrieve a `LocalWaker` instance from an `Arc` of this type. The returned instance will guarantee that the `wake()` and `wake_local` methods of the type which implements `ArcWake` are called, whenever `wake()` is called on a `Waker` or `LocalWaker`. From 4cbbba824b1e35ba88d7fed3f93a479ad57eede6 Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Sun, 18 Nov 2018 11:10:00 -0800 Subject: [PATCH 10/22] Consume LocalWakers on conversion to Waker --- text/0000-futures.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index 4bf01dfa9b5..0f8aea890e6 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -249,8 +249,16 @@ pub struct RawWakerVTable { /// exchange the vtable for that purpose. E.g. it might replace the `wake` /// function with a varient that supports cross-thread wakeups. /// + /// The old `LocalWaker` which contained the data pointer should be consumed + /// while performing the operation. After the operation the `LocalWaker` + /// won't exist anymore, only the new `Waker`. + /// This means that if both instances would utilize the same reference-counted + /// data, changing the reference count would not be necessary. + /// /// If a conversion is not supported, the implementation should return - /// `INVALID_RAW_WAKER`. + /// `INVALID_RAW_WAKER`. In this case the implementation must still make + /// sure that the data pointer which is passed to the function is correctly + /// released, e.g. by calling the associated `drop_fn` on it. pub into_waker: unsafe fn(*const ()) -> RawWaker, /// This function will be called when `wake` is called on the `RawWaker`. pub wake: unsafe fn(*const ()), @@ -355,9 +363,11 @@ impl LocalWaker { /// Converts the `LocalWaker` into `Waker`, which can be sent across /// thread boundaries. /// + /// This operation consumes the `LocalWaker`. + /// /// This function can panic if the associated executor does not support /// getting woken up from a different thread. - pub fn into_waker(&self) -> Waker { + pub fn into_waker(self) -> Waker { match self.try_into_waker() { Some(waker) => waker, None => panic!("Conversion from LocalWaker into Waker is not supported"), @@ -367,11 +377,16 @@ impl LocalWaker { /// Tries to convert the `LocalWaker` into a `Waker`, which can be sent /// across thread boundaries. /// + /// This operation consumes the `LocalWaker`. + /// /// Returns None if the if the associated executor does not support /// getting woken up from a different thread. - pub fn try_into_waker(&self) -> Option { + pub fn try_into_waker(self) -> Option { unsafe { let raw_waker = (self.waker.vtable.into_waker)(self.waker.data); + // Avoid that the drop runs on self, which would e.g. decrease + // the refcount on it. + mem::forget(self); if raw_waker == INVALID_RAW_WAKER { return None; } From de6b949440c32195d821012d8c76c5bd82e4ce82 Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Sun, 18 Nov 2018 17:16:11 -0800 Subject: [PATCH 11/22] Add suggestions around syntax. Let into_waker return an Option. --- text/0000-futures.md | 80 +++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index 0f8aea890e6..da0f1b029ce 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -106,20 +106,20 @@ like with `Iterator`s. Initially these adapters will be provided entirely "out of tree", but eventually they will make their way into the standard library. Ultimately asynchronous computations are executed in the form of *tasks*, -which are comparable to lightweight threads. A `task` is a `()`-producing `Future`, +which are comparable to lightweight threads. A task is a `()`-producing `Future`, which is owned by an *executor*, and polled to completion while the being pinned. -*executor*'s provide the ability to "spawn" such `Future`s. -The implementation of an **executor** schedules the `task` it owns in a cooperative -fashion. It is up to the implementation of an **executor** whether on or more -operation system threads are used for this, as well as how many `task`s can be +*executor*s provide the ability to "spawn" such `Future`s. +The implementation of an executor schedules the task it owns in a cooperative +fashion. It is up to the implementation of an executor whether on or more +operation system threads are used for this, as well as how many tasks can be spawned on them in parallel. This RFC does not include any definition of an executor. It merely defines the -interaction between **executor**s, `task`s and `Future`s, in the form of APIs -that allow `task`s to request getting scheduled again. +interaction between executors, tasks and `Future`s, in the form of APIs +that allow tasks to request getting scheduled again. The `task` module provides these APIs, which are required when manually implementing -`Future`s or **executors**. +`Future`s or executors. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -171,17 +171,17 @@ This functionality is provided through a set of `Waker` types. `Waker`s are objects which are passed as a parameter to the `Future::poll` call, and which can be stored by the implementation of those `Futures`s. Whenever a `Future` has the need to get polled again, it can use the `wake` method of the -waker in order to inform the executor that the `task` which owns the `Future` +waker in order to inform the executor that the task which owns the `Future` should get scheduled and executed again. -The RFC defines 2 concrete `Waker` types, with which implementors of `Futures` +The RFC defines two concrete `Waker` types, with which implementors of `Futures` and asynchronous functions will interact: `Waker` and `LocalWaker`. Both of -these types define a `wake(&self)` function which is used to schedule the `task` +these types define a `wake(&self)` function which is used to schedule the task that is associated to the `Waker` again. The difference between those types is that `Waker` implements the `Send` and `Sync` marker traits, while `LocalWaker` doesn't. This means a `Waker` can be sent to -another thread and stored there in order to wake up the associated `task` later on, +another thread and stored there in order to wake up the associated task later on, while a `LocalWaker` can't be sent. Depending on the capabilities of the underlying executor a `LocalWaker` can be converted into a `Waker`. Most executors in the ecosystem will implement this functionality. The exception will be highly @@ -190,21 +190,21 @@ specialized executors, which e.g. want to avoid the cost of all synchronization. Calling the `wake()` method on a `Waker` will in general be more expensive than on a `LocalWaker` instance, due to additional synchronization. -Executors will always pass a `LocalWaker` instance to the `task`s they poll. +Executors will always pass a `LocalWaker` instance to the tasks they poll. -The mechanism through which `task`s get scheduled again depends on the **executor** -which is driving the `task`. +The mechanism through which tasks get scheduled again depends on the executor +which is driving the task. Possible ways of waking up a an executor include: -- If the **executor** is blocked on a condition variable, the condition variable +- If the executor is blocked on a condition variable, the condition variable needs to get notified. -- If the **executor** is blocked on a system call like `select`, it might need +- If the executor is blocked on a system call like `select`, it might need to get woken up by a syscall like `write` to a pipe. -- If the **executor**'s thread is parked, the wakeup call needs to unpark it. +- If the executor's thread is parked, the wakeup call needs to unpark it. In order to accomodate for this behavior, the `Waker` and `LocalWaker` types are defined through a `RawWaker` type, which is an struct that defines a dynamic dispatch mechanism, which consists of an raw object pointer and a virtual function -pointer table (vtable). This mechanism allows implementors of an **executor** to +pointer table (vtable). This mechanism allows implementors of an executor to customize the behavior of `RawWaker`, `Waker` and `LocalWaker` objects. This mechanism is chosen in favor of trait objects since it allows for more @@ -256,10 +256,10 @@ pub struct RawWakerVTable { /// data, changing the reference count would not be necessary. /// /// If a conversion is not supported, the implementation should return - /// `INVALID_RAW_WAKER`. In this case the implementation must still make + /// `None`. In this case the implementation must still make /// sure that the data pointer which is passed to the function is correctly /// released, e.g. by calling the associated `drop_fn` on it. - pub into_waker: unsafe fn(*const ()) -> RawWaker, + pub into_waker: unsafe fn(*const ()) -> Option, /// This function will be called when `wake` is called on the `RawWaker`. pub wake: unsafe fn(*const ()), /// This function gets called when a `RawWaker` gets dropped. @@ -269,18 +269,6 @@ pub struct RawWakerVTable { pub drop_fn: unsafe fn(*const ()), } -/// A guard object that can be returned by `into_waker` if the implementation -/// wants to signal that a conversion isn't possible. -pub const INVALID_RAW_WAKER: RawWaker = RawWaker { - data: core::ptr::null(), - vtable: &RawWakerVTable { - clone: invalid_fn_call, // Those panic when called - into_waker: invalid_fn_call, - wake: invalid_fn_call, - drop_fn: invalid_fn_call, - }, -}; - /// A `Waker` is a handle for waking up a task by notifying its executor that it /// is ready to be run. /// @@ -383,14 +371,15 @@ impl LocalWaker { /// getting woken up from a different thread. pub fn try_into_waker(self) -> Option { unsafe { - let raw_waker = (self.waker.vtable.into_waker)(self.waker.data); + let maybe_raw_waker = (self.waker.vtable.into_waker)(self.waker.data); // Avoid that the drop runs on self, which would e.g. decrease - // the refcount on it. + // the refcount on it. The object has been consumed or converted + // by the `into_waker` function. mem::forget(self); - if raw_waker == INVALID_RAW_WAKER { - return None; + match maybe_raw_waker { + Some(rw) => Some(Waker::new_unchecked(rw)), + None => None, } - Some(Waker::new_unchecked(raw_waker)) } } @@ -417,14 +406,12 @@ impl LocalWaker { } // The implementations of `Clone` and `Drop` follow what is shown in for `Waker`. - -// TODO: Should `Waker` still implement `From` or potentially `TryFrom`? ``` `Waker`s must fulfill the following requirements: - They must be cloneable -- If all instances of a `Waker` have been dropped and their associated `task` had - been driven to completion, all resources which had been allocated for the `task` +- If all instances of a `Waker` have been dropped and their associated task had + been driven to completion, all resources which had been allocated for the task must have been released. - It must be safe to call `wake()` on a `Waker` even if the associated task has already been driven to completion. @@ -469,9 +456,12 @@ pub trait ArcWake: Send + Sync { /// This function is like wake, but will only be called from the thread on which this /// `ArcWake` was created. /// + /// This function is marked unsafe because callers must guarantee that + /// they call it only on the task on which the `ArcWake` had been created. + /// /// Executors generally maintain a queue of "ready" tasks; `wake_local` should place /// the associated task onto this queue. - fn wake_local(self: &Arc); + unsafe fn wake_local(self: &Arc); /// Creates a `LocalWaker` from an Arc, if T implements ArcWake. /// @@ -509,9 +499,9 @@ impl ArcWake for Task { ``` The use of `&Arc` rather than just `&self` makes it possible to work directly with -the trait object for `Wake`, including cloning it. +the `Arc` that contains the object which implements `ArcWake`. This enables +use-cases like cloning it. -It's possible to construct a `Waker` using `From>`. // TODO: Is that really the case or only `impl Wake`? ## `core::future` module From fdb41f86bd3f722d18e98883064d523f42b3d16b Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Sun, 18 Nov 2018 21:06:17 -0800 Subject: [PATCH 12/22] Allow clone to alter the vtable --- text/0000-futures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index da0f1b029ce..3661186b343 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -242,7 +242,7 @@ pub struct RawWakerVTable { /// The implementation of this function must retain all resources that are /// required for this additional instance of a `RawWaker` and associated /// task. - pub clone: unsafe fn(*const ()) -> *const (), + pub clone: unsafe fn(*const ()) -> RawWaker, /// This function will be called when a `LocalWaker` should be converted into /// a thread-safe `Waker`. The implementation of this function must return /// a new `RawWaker` which can fulfill the requirements of `Waker`. It can From af29613c14f936fbe97868a58177d86b16f987ac Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Mon, 19 Nov 2018 20:34:56 -0800 Subject: [PATCH 13/22] update wording --- text/0000-futures.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index 3661186b343..0b8ac18c2f2 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -106,14 +106,16 @@ like with `Iterator`s. Initially these adapters will be provided entirely "out of tree", but eventually they will make their way into the standard library. Ultimately asynchronous computations are executed in the form of *tasks*, -which are comparable to lightweight threads. A task is a `()`-producing `Future`, -which is owned by an *executor*, and polled to completion while the being pinned. +which are comparable to lightweight threads. *executor*s provide the ability to +create tasks from `()`-producing `Future`s. The executor will pin the `Future` +and `poll` it until completion inside the task that it creates for it. -*executor*s provide the ability to "spawn" such `Future`s. -The implementation of an executor schedules the task it owns in a cooperative -fashion. It is up to the implementation of an executor whether on or more +The implementation of an executor schedules the tasks it owns in a cooperative +fashion. It is up to the implementation of an executor whether one or more operation system threads are used for this, as well as how many tasks can be -spawned on them in parallel. +spawned on it in parallel. Some executor implementations may only be able to +drive a single `Future` to completion, while others can provide the ability to +dynamically accept new `Future`s that are driven to completion inside tasks. This RFC does not include any definition of an executor. It merely defines the interaction between executors, tasks and `Future`s, in the form of APIs @@ -204,8 +206,9 @@ Possible ways of waking up a an executor include: In order to accomodate for this behavior, the `Waker` and `LocalWaker` types are defined through a `RawWaker` type, which is an struct that defines a dynamic dispatch mechanism, which consists of an raw object pointer and a virtual function -pointer table (vtable). This mechanism allows implementors of an executor to -customize the behavior of `RawWaker`, `Waker` and `LocalWaker` objects. +pointer table (vtable). This mechanism allows implementers of an executor to +customize the behavior of `Waker` and `LocalWaker` objects by providing an +executor-specific `RawWaker`. This mechanism is chosen in favor of trait objects since it allows for more flexible memory management schemes. `RawWaker` can be implemented purely in From 04695be32e1255602c3801ed285e9b7fc3129cdd Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Fri, 7 Dec 2018 18:41:10 -0800 Subject: [PATCH 14/22] Apply suggestions from code review Co-Authored-By: Matthias247 --- text/0000-futures.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index 0b8ac18c2f2..08c4b824a7d 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -164,7 +164,7 @@ can be dropped. ### Waking up -If a future can not be directly fulfilled during execution and returns `Pending`, +If a future cannot be directly fulfilled during execution and returns `Pending`, it needs a way to later on inform the executor that it needs to get polled again to make progress. @@ -184,7 +184,7 @@ that is associated to the `Waker` again. The difference between those types is that `Waker` implements the `Send` and `Sync` marker traits, while `LocalWaker` doesn't. This means a `Waker` can be sent to another thread and stored there in order to wake up the associated task later on, -while a `LocalWaker` can't be sent. Depending on the capabilities of the underlying +while a `LocalWaker` cannot be sent. Depending on the capabilities of the underlying executor a `LocalWaker` can be converted into a `Waker`. Most executors in the ecosystem will implement this functionality. The exception will be highly specialized executors, which e.g. want to avoid the cost of all synchronization. @@ -203,7 +203,7 @@ Possible ways of waking up a an executor include: to get woken up by a syscall like `write` to a pipe. - If the executor's thread is parked, the wakeup call needs to unpark it. -In order to accomodate for this behavior, the `Waker` and `LocalWaker` types are +To accommodate this behavior, the `Waker` and `LocalWaker` types are defined through a `RawWaker` type, which is an struct that defines a dynamic dispatch mechanism, which consists of an raw object pointer and a virtual function pointer table (vtable). This mechanism allows implementers of an executor to @@ -220,7 +220,7 @@ The relation between those `Waker` types is outlined in the following definition ```rust /// A `RawWaker` allows the implementor of a task executor to customize the /// behavior of `LocalWaker`s and `Waker`s. -/// It consists of a data pointer a virtual function pointer table (vtable) that +/// It consists of a data pointer and a virtual function pointer table (vtable) that /// customizes the behavior of the `RawWaker`. #[derive(PartialEq)] pub struct RawWaker { @@ -230,7 +230,7 @@ pub struct RawWaker { /// The value of this field gets passed to all functions that are part of /// the vtable as first parameter. pub data: *const (), - /// Virtual function pointer table that customizes the behavior if this waker. + /// Virtual function pointer table that customizes the behavior of this waker. pub vtable: &'static RawWakerVTable, } @@ -287,15 +287,15 @@ impl Waker { /// Wake up the task associated with this `Waker`. pub fn wake(&self) { // The actual wakeup call is delegated through a virtual function call - // to the implementation which is defined by the executor + // to the implementation which is defined by the executor. unsafe { (self.waker.vtable.wake)(self.waker.data) } } - /// Returns whether or not this Waker and other Waker awaken the same task. + /// Returns whether or not this `Waker` and other `Waker` have awaken the same task. /// /// This function works on a best-effort basis, and may return false even - /// when the Wakers would awaken the same task. However, if this function - /// returns true, it is guaranteed that the Wakers will awaken the same task. + /// when the `Waker`s would awaken the same task. However, if this function + /// returns `true`, it is guaranteed that the `Waker`s will awaken the same task. /// /// This function is primarily used for optimization purposes. pub fn will_wake(&self, other: &Waker) -> bool { @@ -304,7 +304,7 @@ impl Waker { /// Creates a new `Waker` from `RawWaker`. /// - /// The method can not check whether `RawWaker` fulfills the required API + /// The method cannot check whether `RawWaker` fulfills the required API /// contract to make it usable for `Waker` and is therefore unsafe. pub unsafe fn new_unchecked(waker: RawWaker) -> Waker { Waker { @@ -338,7 +338,7 @@ impl Drop for Waker { /// This handle encapsulates a `RawWaker` instance, which defines the /// executor-specific wakeup behavior. /// -/// Implements `Clone` +/// Implements `Clone`, but neither `Send` nor `Sync` pub struct LocalWaker { waker: RawWaker, } @@ -399,7 +399,7 @@ impl LocalWaker { /// Creates a new `LocalWaker` from `RawWaker`. /// - /// The method can not check whether `RawWaker` fulfills the required API + /// The method cannot check whether `RawWaker` fulfills the required API /// contract to make it usable for `LocalWaker` and is therefore unsafe. pub unsafe fn new_unchecked(waker: RawWaker) -> LocalWaker { LocalWaker { @@ -412,27 +412,27 @@ impl LocalWaker { ``` `Waker`s must fulfill the following requirements: -- They must be cloneable +- They must be cloneable. - If all instances of a `Waker` have been dropped and their associated task had been driven to completion, all resources which had been allocated for the task must have been released. - It must be safe to call `wake()` on a `Waker` even if the associated task has already been driven to completion. -- `Waker::wake()` must wake up an executor even if it is called from an arbitry +- `Waker::wake()` must wake up an executor even if it is called from an arbitrary thread. An executor which implements `RawWaker` must therefore make sure that all these requirements are fulfilled. -Since many the ownership semantics that are required here can easily be met +Since many of the ownership semantics that are required here can easily be met through a reference-counted `Waker` implementation, a convienence method for defining `Waker`s is provided, which does not require implementing a `RawWaker` and the associated vtable manually. This convience method is based around the `ArcWake` trait. An implementor of an executor can define a type which implements the `ArcWake` trait as defined -below. The `ArcWake` type defines the associated method `into_local_waker`, -which allows to retrieve a `LocalWaker` instance from an `Arc` of this type. +below. The `ArcWake` trait defines the associated method `into_local_waker`, +which affords retrieving a `LocalWaker` instance from an `Arc` of this type. The returned instance will guarantee that the `wake()` and `wake_local` methods of the type which implements `ArcWake` are called, whenever `wake()` is called on a `Waker` or `LocalWaker`. @@ -448,15 +448,15 @@ pub trait ArcWake: Send + Sync { /// Indicates that the associated task is ready to make progress and should /// be `poll`ed. /// - /// This function can called from the thread on which the `ArcWake` was created, - /// as well as from any other thread. + /// This function can be called from an arbitrary thread, including threads which + /// did not create the `ArcWake` based `Waker`. /// /// Executors generally maintain a queue of "ready" tasks; `wake` should place /// the associated task onto this queue. fn wake(self: &Arc); /// Indicates that the associated task is ready to make progress and should be polled. - /// This function is like wake, but will only be called from the thread on which this + /// This function is like `wake`, but will only be called from the thread on which this /// `ArcWake` was created. /// /// This function is marked unsafe because callers must guarantee that @@ -565,8 +565,8 @@ pub trait Future { /// # [`LocalWaker`], [`Waker`] and thread-safety /// /// The `poll` function takes a [`LocalWaker`], an object which knows how to - /// awaken the current task. [`LocalWaker`] is not `Send` nor `Sync`, so in - /// order to make thread-safe futures the [`LocalWaker::into_waker`] and + /// awaken the current task. [`LocalWaker`] is not `Send` nor `Sync`, so + /// to make thread-safe futures the [`LocalWaker::into_waker`] and /// [`LocalWaker::try_into_waker`] methods should be used to convert /// the [`LocalWaker`] into a thread-safe version. /// [`LocalWaker::wake`] implementations have the ability to be more From e025c598fc2df401cfacc3dee1e93ee0643ec43b Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Fri, 7 Dec 2018 20:27:46 -0800 Subject: [PATCH 15/22] Fix ArcWake methods --- text/0000-futures.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index 08c4b824a7d..d3018dde8ce 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -453,7 +453,7 @@ pub trait ArcWake: Send + Sync { /// /// Executors generally maintain a queue of "ready" tasks; `wake` should place /// the associated task onto this queue. - fn wake(self: &Arc); + fn wake(arc_self: &Arc); /// Indicates that the associated task is ready to make progress and should be polled. /// This function is like `wake`, but will only be called from the thread on which this @@ -464,7 +464,9 @@ pub trait ArcWake: Send + Sync { /// /// Executors generally maintain a queue of "ready" tasks; `wake_local` should place /// the associated task onto this queue. - unsafe fn wake_local(self: &Arc); + unsafe fn wake_local(arc_self: &Arc) { + Self::wake(arc_self); + } /// Creates a `LocalWaker` from an Arc, if T implements ArcWake. /// @@ -565,7 +567,7 @@ pub trait Future { /// # [`LocalWaker`], [`Waker`] and thread-safety /// /// The `poll` function takes a [`LocalWaker`], an object which knows how to - /// awaken the current task. [`LocalWaker`] is not `Send` nor `Sync`, so + /// awaken the current task. [`LocalWaker`] is not `Send` nor `Sync`, so /// to make thread-safe futures the [`LocalWaker::into_waker`] and /// [`LocalWaker::try_into_waker`] methods should be used to convert /// the [`LocalWaker`] into a thread-safe version. From db5f882978b710ad7da71c49d54f63ec188f3484 Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Fri, 7 Dec 2018 20:40:43 -0800 Subject: [PATCH 16/22] Add additional clarification around RawWaker --- text/0000-futures.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index d3018dde8ce..55a9e41b1e7 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -242,9 +242,13 @@ pub struct RawWaker { pub struct RawWakerVTable { /// This function will be called when the `RawWaker` gets cloned, e.g. when /// the `Waker` or `LocalWaker` in which the `RawWaker` is stored gets cloned. + /// /// The implementation of this function must retain all resources that are /// required for this additional instance of a `RawWaker` and associated - /// task. + /// task. The implementation must return a valid `RawWaker` that behaves + /// equivalent to the `RawWaker` that got cloned. E.g. cloning a `RawWaker` + /// that implemented a thread-safe wakeup for use in `Waker` must return + /// a `RawWaker` that implements the same wakeup behavior. pub clone: unsafe fn(*const ()) -> RawWaker, /// This function will be called when a `LocalWaker` should be converted into /// a thread-safe `Waker`. The implementation of this function must return @@ -266,6 +270,7 @@ pub struct RawWakerVTable { /// This function will be called when `wake` is called on the `RawWaker`. pub wake: unsafe fn(*const ()), /// This function gets called when a `RawWaker` gets dropped. + /// /// The implementation of this function must make sure to release any /// resources that are associated with this instance of a `RawWaker` and /// associated task. @@ -421,8 +426,8 @@ impl LocalWaker { - `Waker::wake()` must wake up an executor even if it is called from an arbitrary thread. -An executor which implements `RawWaker` must therefore make sure that all these -requirements are fulfilled. +An executor that instantiates a `RawWaker` must therefore make sure that all +these requirements are fulfilled. Since many of the ownership semantics that are required here can easily be met through a reference-counted `Waker` implementation, a convienence method for From e1d2b8f063e6abcc163893c15f2b49386388cd66 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Tue, 8 Jan 2019 15:45:04 -0800 Subject: [PATCH 17/22] Remove LocalWaker and simplify the RawWakerVTable --- text/0000-futures.md | 316 +++++++++++-------------------------------- 1 file changed, 78 insertions(+), 238 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index 55a9e41b1e7..31a29090b5d 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -1,4 +1,4 @@ -- Feature Name: (fill me in with a unique ident, my_awesome_feature) +- Feature Name: futures_api - Start Date: 2018-11-09 - RFC PR: (leave this empty) - Rust Issue: (leave this empty) @@ -176,23 +176,10 @@ and which can be stored by the implementation of those `Futures`s. Whenever a waker in order to inform the executor that the task which owns the `Future` should get scheduled and executed again. -The RFC defines two concrete `Waker` types, with which implementors of `Futures` -and asynchronous functions will interact: `Waker` and `LocalWaker`. Both of -these types define a `wake(&self)` function which is used to schedule the task -that is associated to the `Waker` again. - -The difference between those types is that `Waker` implements the `Send` and `Sync` -marker traits, while `LocalWaker` doesn't. This means a `Waker` can be sent to -another thread and stored there in order to wake up the associated task later on, -while a `LocalWaker` cannot be sent. Depending on the capabilities of the underlying -executor a `LocalWaker` can be converted into a `Waker`. Most executors in the -ecosystem will implement this functionality. The exception will be highly -specialized executors, which e.g. want to avoid the cost of all synchronization. - -Calling the `wake()` method on a `Waker` will in general be more expensive than -on a `LocalWaker` instance, due to additional synchronization. - -Executors will always pass a `LocalWaker` instance to the tasks they poll. +The RFC defines a concrete `Waker` types with which implementors of `Futures` +and asynchronous functions will interact. This type defines a `wake(&self)` +function which is used to schedule the task that is associated to the `Waker` +to be polled again. The mechanism through which tasks get scheduled again depends on the executor which is driving the task. @@ -203,23 +190,25 @@ Possible ways of waking up a an executor include: to get woken up by a syscall like `write` to a pipe. - If the executor's thread is parked, the wakeup call needs to unpark it. -To accommodate this behavior, the `Waker` and `LocalWaker` types are -defined through a `RawWaker` type, which is an struct that defines a dynamic -dispatch mechanism, which consists of an raw object pointer and a virtual function -pointer table (vtable). This mechanism allows implementers of an executor to -customize the behavior of `Waker` and `LocalWaker` objects by providing an -executor-specific `RawWaker`. +To allow executors to implement custom wakeup behavior, the `Waker` type +internally contains a type called `RawWaker`, which consists of a pointer +to a custom wakeable object and a reference to a virtual function +pointer table (vtable) which provides functions to `clone`, `wake`, and +`drop` the underlying wakeable object. This mechanism is chosen in favor of trait objects since it allows for more flexible memory management schemes. `RawWaker` can be implemented purely in terms of global functions and state, on top of reference counted objects, or -in other ways. +in other ways. This strategy also makes it easier to provide different vtable +functions that will perform different behaviors despite referencing the same +underlying wakeable object type. The relation between those `Waker` types is outlined in the following definitions: ```rust -/// A `RawWaker` allows the implementor of a task executor to customize the -/// behavior of `LocalWaker`s and `Waker`s. +/// A `RawWaker` allows the implementor of a task executor to create a `Waker` +/// which provides customized wakeup behavior. +/// /// It consists of a data pointer and a virtual function pointer table (vtable) that /// customizes the behavior of the `RawWaker`. #[derive(PartialEq)] @@ -234,41 +223,26 @@ pub struct RawWaker { pub vtable: &'static RawWakerVTable, } -/// A virtual function pointer table (vtable) that allows to customize the -/// behavior of a `RawWaker`. -/// The pointers which get passed to all functions inside the vtable are the -/// values of the `data` field. +/// A virtual function pointer table (vtable) that specifies the behavior +/// of a `RawWaker`. +/// +/// The pointer passed to all functions inside the vtable is the `data` pointer +/// from the enclosing `RawWaker` object. #[derive(PartialEq, Copy, Clone)] pub struct RawWakerVTable { /// This function will be called when the `RawWaker` gets cloned, e.g. when - /// the `Waker` or `LocalWaker` in which the `RawWaker` is stored gets cloned. + /// the `Waker` in which the `RawWaker` is stored gets cloned. /// /// The implementation of this function must retain all resources that are /// required for this additional instance of a `RawWaker` and associated - /// task. The implementation must return a valid `RawWaker` that behaves - /// equivalent to the `RawWaker` that got cloned. E.g. cloning a `RawWaker` - /// that implemented a thread-safe wakeup for use in `Waker` must return - /// a `RawWaker` that implements the same wakeup behavior. + /// task. Calling `wake` on the resulting `RawWaker` should result in a wakeup + /// of the same task that would have been awoken by the original `RawWaker`. pub clone: unsafe fn(*const ()) -> RawWaker, - /// This function will be called when a `LocalWaker` should be converted into - /// a thread-safe `Waker`. The implementation of this function must return - /// a new `RawWaker` which can fulfill the requirements of `Waker`. It can - /// exchange the vtable for that purpose. E.g. it might replace the `wake` - /// function with a varient that supports cross-thread wakeups. - /// - /// The old `LocalWaker` which contained the data pointer should be consumed - /// while performing the operation. After the operation the `LocalWaker` - /// won't exist anymore, only the new `Waker`. - /// This means that if both instances would utilize the same reference-counted - /// data, changing the reference count would not be necessary. - /// - /// If a conversion is not supported, the implementation should return - /// `None`. In this case the implementation must still make - /// sure that the data pointer which is passed to the function is correctly - /// released, e.g. by calling the associated `drop_fn` on it. - pub into_waker: unsafe fn(*const ()) -> Option, - /// This function will be called when `wake` is called on the `RawWaker`. + + /// This function will be called when `wake` is called on the `Waker`. + /// It must wake up the task associated with this `RawWaker`. pub wake: unsafe fn(*const ()), + /// This function gets called when a `RawWaker` gets dropped. /// /// The implementation of this function must make sure to release any @@ -334,86 +308,6 @@ impl Drop for Waker { unsafe { (self.waker.vtable.drop_fn)(self.waker.data) } } } - -/// A `LocalWaker` is a handle for waking up a task by notifying its executor that it is ready to be run. -/// -/// This is similar to the `Waker` type, but cannot be sent across threads. -/// Task executors can use this type to implement more optimized singlethreaded wakeup behavior. -/// -/// This handle encapsulates a `RawWaker` instance, which defines the -/// executor-specific wakeup behavior. -/// -/// Implements `Clone`, but neither `Send` nor `Sync` -pub struct LocalWaker { - waker: RawWaker, -} - -impl LocalWaker { - /// Wake up the task associated with this `LocalWaker`. - pub fn wake(&self) { - // The actual wakeup call is delegated through a virtual function call - // to the implementation which is defined by the executor - unsafe { (self.waker.vtable.wake)(self.waker.data) } - } - - /// Converts the `LocalWaker` into `Waker`, which can be sent across - /// thread boundaries. - /// - /// This operation consumes the `LocalWaker`. - /// - /// This function can panic if the associated executor does not support - /// getting woken up from a different thread. - pub fn into_waker(self) -> Waker { - match self.try_into_waker() { - Some(waker) => waker, - None => panic!("Conversion from LocalWaker into Waker is not supported"), - } - } - - /// Tries to convert the `LocalWaker` into a `Waker`, which can be sent - /// across thread boundaries. - /// - /// This operation consumes the `LocalWaker`. - /// - /// Returns None if the if the associated executor does not support - /// getting woken up from a different thread. - pub fn try_into_waker(self) -> Option { - unsafe { - let maybe_raw_waker = (self.waker.vtable.into_waker)(self.waker.data); - // Avoid that the drop runs on self, which would e.g. decrease - // the refcount on it. The object has been consumed or converted - // by the `into_waker` function. - mem::forget(self); - match maybe_raw_waker { - Some(rw) => Some(Waker::new_unchecked(rw)), - None => None, - } - } - } - - /// Returns whether or not this LocalWaker and other LocalWaker awaken the same task. - /// - /// This function works on a best-effort basis, and may return false even - /// when the LocalWakers would awaken the same task. However, if this function - /// returns true, it is guaranteed that the LocalWakers will awaken the same task. - /// - /// This function is primarily used for optimization purposes. - pub fn will_wake(&self, other: &LocalWaker) -> bool { - self.waker == other.waker - } - - /// Creates a new `LocalWaker` from `RawWaker`. - /// - /// The method cannot check whether `RawWaker` fulfills the required API - /// contract to make it usable for `LocalWaker` and is therefore unsafe. - pub unsafe fn new_unchecked(waker: RawWaker) -> LocalWaker { - LocalWaker { - waker: waker, - } - } -} - -// The implementations of `Clone` and `Drop` follow what is shown in for `Waker`. ``` `Waker`s must fulfill the following requirements: @@ -429,90 +323,6 @@ impl LocalWaker { An executor that instantiates a `RawWaker` must therefore make sure that all these requirements are fulfilled. -Since many of the ownership semantics that are required here can easily be met -through a reference-counted `Waker` implementation, a convienence method for -defining `Waker`s is provided, which does not require implementing a `RawWaker` -and the associated vtable manually. - -This convience method is based around the `ArcWake` trait. An implementor of -an executor can define a type which implements the `ArcWake` trait as defined -below. The `ArcWake` trait defines the associated method `into_local_waker`, -which affords retrieving a `LocalWaker` instance from an `Arc` of this type. -The returned instance will guarantee that the `wake()` and `wake_local` methods -of the type which implements `ArcWake` are called, whenever `wake()` is called -on a `Waker` or `LocalWaker`. - -```rust -/// A way of waking up a specific task. -/// -/// By implementing this trait, types that are expected to be wrapped in an `Arc` -/// can be converted into `LocalWaker` and `Waker` objects. -/// Those Wakers can be used to signal executors that a task it owns -/// is ready to be `poll`ed again. -pub trait ArcWake: Send + Sync { - /// Indicates that the associated task is ready to make progress and should - /// be `poll`ed. - /// - /// This function can be called from an arbitrary thread, including threads which - /// did not create the `ArcWake` based `Waker`. - /// - /// Executors generally maintain a queue of "ready" tasks; `wake` should place - /// the associated task onto this queue. - fn wake(arc_self: &Arc); - - /// Indicates that the associated task is ready to make progress and should be polled. - /// This function is like `wake`, but will only be called from the thread on which this - /// `ArcWake` was created. - /// - /// This function is marked unsafe because callers must guarantee that - /// they call it only on the task on which the `ArcWake` had been created. - /// - /// Executors generally maintain a queue of "ready" tasks; `wake_local` should place - /// the associated task onto this queue. - unsafe fn wake_local(arc_self: &Arc) { - Self::wake(arc_self); - } - - /// Creates a `LocalWaker` from an Arc, if T implements ArcWake. - /// - /// The returned `LocalWaker` will call `wake.wake_local()` when awoken. - /// - /// The returned `LocalWaker` can be converted into a `Waker` through - /// it's `into_waker()` method. If `wake()` is called on this `Waker`, - /// the `wake()` function that is defined inside this trait will get called. - fn into_local_waker(wake: Arc) -> LocalWaker where Self: Sized; -} -``` - -To see how this might be used in practice, here's a simple example sketch: - -```rust -struct ExecutorInner { - sync_ready_queue: SynchronousQueue, - optimized_queue: UnsafeCell>, -} - -struct Task { - future: ..., - executor: Arc, -} - -impl ArcWake for Task { - fn wake(self: &Arc) { - self.executor.sync_ready_queue.push(self.clone()); - } - - fn wake_local(self: &Arc) { - (&mut *self.executor.optimized_queue.get()).push(self.clone()) - } -} -``` - -The use of `&Arc` rather than just `&self` makes it possible to work directly with -the `Arc` that contains the object which implements `ArcWake`. This enables -use-cases like cloning it. - - ## `core::future` module With all of the above task infrastructure in place, defining `Future` is @@ -537,16 +347,16 @@ pub trait Future { /// Once a future has finished, clients should not `poll` it again. /// /// When a future is not ready yet, `poll` returns `Poll::Pending` and - /// stores a clone of the [`LocalWaker`] to be woken once the future can + /// stores a clone of the [`Waker`] to be woken once the future can /// make progress. For example, a future waiting for a socket to become - /// readable would call `.clone()` on the [`LocalWaker`] and store it. + /// readable would call `.clone()` on the [`Waker`] and store it. /// When a signal arrives elsewhere indicating that the socket is readable, - /// `[LocalWaker::wake]` is called and the socket future's task is awoken. + /// `[Waker::wake]` is called and the socket future's task is awoken. /// Once a task has been woken up, it should attempt to `poll` the future /// again, which may or may not produce a final value. /// /// Note that on multiple calls to `poll`, only the most recent - /// [`LocalWaker`] passed to `poll` should be scheduled to receive a + /// [`Waker`] passed to `poll` should be scheduled to receive a /// wakeup. /// /// # Runtime characteristics @@ -569,17 +379,6 @@ pub trait Future { /// thread pool (or something similar) to ensure that `poll` can return /// quickly. /// - /// # [`LocalWaker`], [`Waker`] and thread-safety - /// - /// The `poll` function takes a [`LocalWaker`], an object which knows how to - /// awaken the current task. [`LocalWaker`] is not `Send` nor `Sync`, so - /// to make thread-safe futures the [`LocalWaker::into_waker`] and - /// [`LocalWaker::try_into_waker`] methods should be used to convert - /// the [`LocalWaker`] into a thread-safe version. - /// [`LocalWaker::wake`] implementations have the ability to be more - /// efficient, however, so when thread safety is not necessary, - /// [`LocalWaker`] should be preferred. - /// /// # Panics /// /// Once a future has completed (returned `Ready` from `poll`), @@ -589,11 +388,10 @@ pub trait Future { /// /// [`Poll::Pending`]: ../task/enum.Poll.html#variant.Pending /// [`Poll::Ready(val)`]: ../task/enum.Poll.html#variant.Ready - /// [`LocalWaker`]: ../task/struct.LocalWaker.html - /// [`LocalWaker::into_waker`]: ../task/struct.LocalWaker.html#method.into_waker - /// [`LocalWaker::wake`]: ../task/struct.LocalWaker.html#method.wake /// [`Waker`]: ../task/struct.Waker.html - fn poll(self: Pin<&mut Self>, lw: &LocalWaker) -> Poll; + /// [`Waker::into_waker`]: ../task/struct.Waker.html#method.into_waker + /// [`Waker::wake`]: ../task/struct.Waker.html#method.wake + fn poll(self: Pin<&mut Self>, lw: &Waker) -> Poll; } ``` @@ -621,7 +419,7 @@ important rationale is to provide an orthogonal, compositional semantics for `as that mirrors normal `fn`, rather than *also* baking in a particular style of error handling. -- Passing a `LocalWaker` explicitly, rather than stashing it in thread-local storage. +- Passing a `Waker` explicitly, rather than stashing it in thread-local storage. This has been a hotly debated issue since futures 0.1 was released, and this RFC does not seek to relitigate it, but to summarize, the major advantages are (1) when working with manual futures (as opposed to `async` blocks) it's much easier to @@ -756,6 +554,48 @@ It's possible to add `MoveFuture`, together with a blanket impl, at any point in Thus, starting with just the single `Future` trait as proposed in this RFC keeps our options maximally open while we gain experience. +## Rationale, drawbacks and alternatives to the wakeup design (`Waker`) + +Previous iterations of this proposal included a separate wakeup type, +`LocalWaker`, which was `!Send + !Sync` and could be used to implement +optimized executor behavior without requiring atomic reference counting +or atomic wakeups. However, in practice, these same optimizations are +available through the use of thread-local wakeup queues, carrying IDs +rather than pointers to wakeup objects, and tracking an executor ID or +thread ID to perform a runtime assertion that a `Waker` wasn't sent across +threads. For a simple example, a single thread-locked executor with zero +atomics can be implemented as follows: + +```rust +struct Executor { + // map from task id (usize) to task + tasks: Slab, + // list of woken tasks to poll + work_queue: VecDeque, +} + +thread_local! { + pub static EXECUTOR: RefCell> = ...; +} + +static VTABLE: &RawWakerVTable = &RawWakerVTable { + clone: |data: *const ()| RawWaker { data, vtable: VTABLE, }, + wake: |data: *const ()| EXECUTOR.borrow_mut().as_mut().expect(...).work_queue.push(data as usize), + drop: |_: *const ()| {}, +}; +``` + +While this solution gives inferior error messages to the `LocalWaker` approach +(since it can't panic until `wake` occurs on the wrong thread, rather than +panicking when `LocalWaker` is transformed into a `Waker`), it dramatically +simplifies the user-facing API by de-duplicating the `LocalWaker` and `Waker` +types. + +In practice, it's also likely that the most common executors in the Rust +ecosystem will continue to be multithreaded-compatible (as they are today), +so optimizing for the ergonomics of this case is prioritized over better +error messages in the more heavily specialized case. + # Prior art [prior-art]: #prior-art From af3da95180b2db6fd1745ef07bd94de3e60b08e6 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Wed, 23 Jan 2019 10:02:52 -0800 Subject: [PATCH 18/22] Cleanup --- text/0000-futures.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index 31a29090b5d..d8a1aacae12 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -176,9 +176,9 @@ and which can be stored by the implementation of those `Futures`s. Whenever a waker in order to inform the executor that the task which owns the `Future` should get scheduled and executed again. -The RFC defines a concrete `Waker` types with which implementors of `Futures` +The RFC defines a concrete `Waker` type with which implementors of `Futures` and asynchronous functions will interact. This type defines a `wake(&self)` -function which is used to schedule the task that is associated to the `Waker` +method which is used to schedule the task that is associated to the `Waker` to be polled again. The mechanism through which tasks get scheduled again depends on the executor @@ -191,7 +191,7 @@ Possible ways of waking up a an executor include: - If the executor's thread is parked, the wakeup call needs to unpark it. To allow executors to implement custom wakeup behavior, the `Waker` type -internally contains a type called `RawWaker`, which consists of a pointer +contains a type called `RawWaker`, which consists of a pointer to a custom wakeable object and a reference to a virtual function pointer table (vtable) which provides functions to `clone`, `wake`, and `drop` the underlying wakeable object. @@ -295,10 +295,7 @@ impl Waker { impl Clone for Waker { fn clone(&self) -> Self { Waker { - waker: RawWaker { - data: unsafe { (self.waker.vtable.clone)(self.waker.data) }, - vtable: self.waker.vtable, - } + waker: unsafe { (self.waker.vtable.clone)(self.waker.data) }, } } } From a268c75a291b25341505df01c13637ec993b5547 Mon Sep 17 00:00:00 2001 From: John Smith Date: Sat, 26 Jan 2019 16:46:23 +0800 Subject: [PATCH 19/22] typo fix --- text/0000-futures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index d8a1aacae12..2d154794f71 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -32,7 +32,7 @@ that goes through its importance in greater detail. As with closures, `async` syntax involves producing an anonymous type that implements a key trait: `Future`. Because `async`/`await` requires language-level support, the underlying trait must also be part of the standard library. Thus, the goal -of this RFC is to stabilize this this `Future` trait and the types it depends on. +of this RFC is to stabilize this `Future` trait and the types it depends on. This is the last step needed before we are in a position to stabilize `async`/`await` itself. From 834f0cdc154b24e46816e8388c1b12ab20e81a73 Mon Sep 17 00:00:00 2001 From: John Smith Date: Sat, 26 Jan 2019 18:38:13 +0800 Subject: [PATCH 20/22] typo --- text/0000-futures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index d8a1aacae12..5e00e9f0238 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -183,7 +183,7 @@ to be polled again. The mechanism through which tasks get scheduled again depends on the executor which is driving the task. -Possible ways of waking up a an executor include: +Possible ways of waking up an executor include: - If the executor is blocked on a condition variable, the condition variable needs to get notified. - If the executor is blocked on a system call like `select`, it might need From f50c0c4d535c4604641454261e252795af18815a Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Mon, 11 Mar 2019 12:04:38 -0700 Subject: [PATCH 21/22] Apply suggestions from code review Co-Authored-By: cramertj --- text/0000-futures.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/text/0000-futures.md b/text/0000-futures.md index 77a06f753bb..e66c183f0d8 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -386,9 +386,8 @@ pub trait Future { /// [`Poll::Pending`]: ../task/enum.Poll.html#variant.Pending /// [`Poll::Ready(val)`]: ../task/enum.Poll.html#variant.Ready /// [`Waker`]: ../task/struct.Waker.html - /// [`Waker::into_waker`]: ../task/struct.Waker.html#method.into_waker /// [`Waker::wake`]: ../task/struct.Waker.html#method.wake - fn poll(self: Pin<&mut Self>, lw: &Waker) -> Poll; + fn poll(self: Pin<&mut Self>, waker: &Waker) -> Poll; } ``` @@ -578,7 +577,7 @@ thread_local! { static VTABLE: &RawWakerVTable = &RawWakerVTable { clone: |data: *const ()| RawWaker { data, vtable: VTABLE, }, wake: |data: *const ()| EXECUTOR.borrow_mut().as_mut().expect(...).work_queue.push(data as usize), - drop: |_: *const ()| {}, + drop, }; ``` From e7eaea194994da28bde2c36d78fedf50e79b4bcf Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Mon, 11 Mar 2019 12:59:11 -0700 Subject: [PATCH 22/22] RFC 2592 --- text/{0000-futures.md => 2592-futures.md} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename text/{0000-futures.md => 2592-futures.md} (99%) diff --git a/text/0000-futures.md b/text/2592-futures.md similarity index 99% rename from text/0000-futures.md rename to text/2592-futures.md index e66c183f0d8..71c4e51c464 100644 --- a/text/0000-futures.md +++ b/text/2592-futures.md @@ -1,7 +1,7 @@ -- Feature Name: futures_api +- Feature Name: `futures_api` - Start Date: 2018-11-09 -- RFC PR: (leave this empty) -- Rust Issue: (leave this empty) +- RFC PR: [rust-lang/rfcs#2592](https://github.com/rust-lang/rfcs/pull/2592) +- Rust Issue: [rust-lang/rust#59113](https://github.com/rust-lang/rust/issues/59113) # Summary [summary]: #summary