From 8d44cff58b368ca60eb0a21381a32e364e73d1f5 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 10 Oct 2024 12:53:31 -0400 Subject: [PATCH 01/11] introduce the color syntactic design pattern --- text/0000-color-syntactic-design-pattern.md | 761 ++++++++++++++++++++ 1 file changed, 761 insertions(+) create mode 100644 text/0000-color-syntactic-design-pattern.md diff --git a/text/0000-color-syntactic-design-pattern.md b/text/0000-color-syntactic-design-pattern.md new file mode 100644 index 00000000000..a6d4f51b115 --- /dev/null +++ b/text/0000-color-syntactic-design-pattern.md @@ -0,0 +1,761 @@ +--- +title: 'RFC: The "color" syntactic design pattern' + +--- + +# RFC: The "color" syntactic design pattern + +[#67792]: https://github.com/rust-lang/rust/issues/67792 +[RFC #3628]: https://github.com/rust-lang/rfcs/pull/3628 +[RFC #3668]: https://github.com/rust-lang/rfcs/pull/3668 +[RFC #243]: https://github.com/rust-lang/rfcs/pull/243 +[RFC #2071]: https://github.com/rust-lang/rfcs/blob/master/text/2071-impl-trait-existential-types.md +[asyncfg]: https://rust-lang.github.io/rust-project-goals/2024h2/async.html +[droporder]: https://github.com/rust-lang/rust/blob/0b16baa570d26224612ea27f76d68e4c6ca135cc/compiler/rustc_ast_lowering/src/item.rs#L1179-L1210 +[WCIF]: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ + +# Summary +[summary]: #summary + +This RFC unblock the stabilization of async closures by committing to `K $Trait` (where `K` is some keyword like `async` or `const`) as a pattern that we will use going forward to define a "K-variant of `Trait`". This commitment is made as part of committing to a larger *syntactic design pattern* called **the color pattern**. The color pattern is "advisory". It details all the parts that a "color-like keyword" should have and suggests specific syntax that should be used, but it is not itself a language feature. + +In the color pattern, each color is tied to a specific keyword `K`. Colors share the "infectious property": code with color `K` interacts naturally with other code with color `K` but only interacts in limited ways with code without the color `K`. Every color keyword `K` should support at least the following: + +* `K`-functions using the syntax `K fn $name() -> $ty`; +* `K`-blocks using the syntax `K { $expr }` (and potentially `K move { $expr }`); +* `K`-traits using the syntax `K $Trait`; + * `K`-colored traits should offer at least the same methods, associated types, and other trait items as an uncolored version of the trait. Some of the items will be `K`-colored, but not necessarily all of them. +* `K`-closures using the syntax `K [move] |$args| $expr` to define a K-closure; + * Closures implement the `K Fn` traits. + +Some colors rewrite code so that it executes differently (e.g., `async`). These are called **rewrite colors**. Each such color should have the following: + +* A syntax `🚲K<$ty>` defining the `K`-type, the type that results from a `K`-block, `K`-function, or `K`-closure whose body has type `$ty`. + * The `🚲K<$ty>` is a placeholder. We expect a future RFC to define the actual syntax. +* A "do" operation that, when executed in a `K`-block, consumes a `🚲K<$ty>` and produces a `$ty` value. +* The property that a `K`-function can be transformed to a regular function with a `K`-colored return type and body. + * i.e., the following are roughly equivalent (the precise translation can vary so as to e.g. preserve drop order): + * `K fn $name($args) -> $ty { $expr }` + * `fn $name($args) -> 🚲K<$ty> { K { $expr } }` + +## Binding recommendations + +Existing color-like keywords in the language do not have all of these parts. The RFC therefore includes a limited set of binding recommendations that brings them closer to conformance: + +* Commit to `K $Trait` as the syntax for applying colors to traits, with the `async Fn`, `async FnMut`, and `async FnOnce` traits being the only current usable example. +* Commit to adding a TBD syntax `🚲async<$ty>` that will meet the equivalences described in this RFC. + +## Not part of this RFC + +The [Future Possibilities](#future-possibilities) discusses other changes we could make to make existing and planned colors fit the pattern better. Examples of things that this RFC does NOT specify (but which early readers thought it might): + +* Any form of "effect" or "color" generics: + * Colors in this RFC are a pattern for Rust designers to keep in mind as we explore possible language features, not a first-class language feature; this RFC also does not close the door on making them a first-class feature in the future. +* Whether or how `async` can be used with traits beyond the `Fn` traits: + * For example, the RFC specifies that **if** we add an async version of the `Read` trait, it will be referred to as `async Read`, but the RFC does **not** specify whether to add such a trait nor how such a trait would be defined or what its contents would be. +* How `const Trait` ought to work ([under active exploration][#67792]): + * The RFC only specifies that the syntax for naming a `const`-colored trait should be `const Trait`; it does not specify what a `const`-colored trait would mean or when that syntax can be used. +* What specific syntax we should use for `🚲K<$ty>`: + * We are committed to adding this syntax at least for `async`, but the precise syntax still needs to be pinned down. [RFC #3628][] contains one possibility. + +# Motivation +[motivation]: #motivation + +The Rust Project has a [flagship goal][asyncfg] of bringing the async Rust experience closer to parity with sync Rust, and one of the most important deliverables for that goal is [async closures][RFC #3668]. The only unresolved question in the async closure RFC is the syntax for async closure bounds, which could be spelled like `F: async Fn()` or `F: AsyncFn()` (or many other ways). For reasons covered more in depth in that RFC, neither of these is an obvious choice given the Rust we have today. + +## Consensus: `async Fn` is nice, but only as part of a larger design pattern + +The lang team discussed the syntax question [in a design meeting][DM] and concluded that we prefer offering the `async Fn` syntax as a means of requesting the "async version of a trait", but we would want it to be part of a consistent **syntactic design pattern** for selecting variants of traits. + +[DM]: https://hackmd.io/@rust-lang-team/rJxAOyWaC + +In other words, if users wrote `async Fn` to get an async version of a closure but `AsyncRead` to get the async version of the `Read` trait, that would be confusing. In contrast, if the pattern to get the async version of a trait is always to write the `async` keyword (e.g., `async Fn` and `async Read`), that is appealing. This is true even if the `async Read` trait is defined in a very separate way from its sync counterpart. + +We also observed that there is a need for having "variants of traits" in other similar contexts, most notably `const`, where there is [active experimentation for a const-trait design][#67792]. If we further extend the pattern to cover those cases, so that one writes (e.g.) `const Fn` or `const Default` to the "const versions of the `Fn` or `Default` traits" respectively, then these two instances of the same pattern reinforce one another, helping to create a coherent whole. Any future instances we add would only strenghten this effect. + +## A latent set of "colors" exists in Rust today + +Looking more closely at `async` and `const`, we see that there is a latent concept within Rust that we refer to as **colors**. The term color is a playful reference to the famous ["What color is your function?"][WCIF] blog post. + +Each color has an associated keyword `K` that can be applied to functions and blocks. Code with the color `K` interoperates fully with other code with the same color; working across colors is generally more limited: + +* `async` functions return an `impl Future` which can only be `await`'d from another `async` block. +* `unsafe` functions can only be called from `unsafe` blocks or other `unsafe` functions. +* `const` functions can only call other `const` functions. + +## Tenet: consistent syntax and transformations makes Rust easier to learn + +The premise of this RFC is that color keywords like `const`, `async`, and `unsafe` "feel similar" to users and that we should make them work as consistently as possible (but no more than that). Because they serve different purposes, we don't wish to force them into a single shape if that shape is an ill-fit. Nonetheless, we wish to ensure that these colors, and any future "color-like" features, feel as consistent and predictable as possible, so that users can develop "muscle memory" that helps them to predict syntax they haven't used or seen yet. The RFC describes the syntax to use for a color keyword applied to various constructs as well as some of the "approximate equivalences" that colors should generally hold. + +## Role of this RFC: identify and describe this pattern + +Code with a given color `K` is meant to interoperate fully with other code with the same color, but in practice that is not the case. This is precisely the gaps we are aiming to close by considering features like [async closures][RFC #3668] or [const traits][#67792]. The role of this RFC is to identify the "color pattern" and carry it to its logical conclusion. + +Existing colors do not yet support all the aspects described herein. This RFC recommends changes that close a limited number of these gaps; the [Future Possibilities](#future-possibilities) discusses other changes we could make to more fully support the color pattern for every color. + +## Tenet: all parts of the color pattern should have explicit syntax centered on the keyword `K` + +One of the recommendations of this RFC is committing to add a `K`-colored type syntax in the future, similar to what is proposed in [RFC #3628][]. The motivation for this is that it is important when teaching colors to be able to teach the *color specifically*, without having to reference additional language features like `impl Trait`. This addresses feedback we have received from Rust trainers which is that one of the difficulties in teaching Rust is that users can't learn Rust in "layers", they have to learn all parts of Rust at once before any of it makes sense. + +## Tenet: no false equivalence, but partial consistency is better than none + +Just because colors share many elements doesn't make them equivalent. We also describe how the color pattern applies to `const` and show why some parts of the pattern don't make sense in that context. We think that `const` should use the pattern where it makes sense, but avoid it (or use a distinct pattern) where it does not fit. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Our guide-level explanation proceeds in three phases. We begin by **defining the color pattern** for **rewrite colors**, using `async` as our running example. A *rewrite color* `K` is one that, like `async`, transforms the result type for `K`-colored functions and blocks into a distinct type, reflecting the impact of the color. + +Next, we define a **subset of the color pattern** appropriate for **filter colors**. A *filter color* `K` is one that, like `const` or `unsafe`, tracks the presence of absence of kinds of operations. Since filter colors do not change how code executes, they also do not change the result type of a `K`-block, so only some parts of the color pattern are relevant. + +Finally, we close with a section that explains the `async` color, making use of the various parts of the pattern described in the first section. + +## Defining the "color pattern" + +### When is a keyword `K` a color? + +We begin by defining the criteria where we, as language designers, should consider using the color pattern for a keyword `K`. Given the color patterns informality, there is no hard and fast rule for this, but the primary criteria to consider are as follows: + +1. The keyword `K` is used to "color" code, in the form of functions and blocks. +2. `K`-colored code works best when used with other `K`-colored code; working with "uncolored" code is limited. + +Apart from these minimal criteria, colors can encompass a broad range of keywords with diverse effects. In Rust today, there are three keywords that meet this definition: `async`, `const`, and `unsafe`. This section focuses on `async`; the next section covers `const` and `unsafe`. + +### `K`-colored functions, blocks, traits, and closures + +The syntax to use when applying colors to functions, blocks, traits, and closures is as follows: + +* `K fn $name($args) -> $ty { $expr }` for functions +* `K { $statements; $expr }` (and in some cases `K move { $statements; $expr }`) for blocks +* `K $Trait` for traits (generalizing the precedent set by [RFC #3668][]) +* `K |$args| $expr` for closures (generalizing the precedent set by [RFC #3668][]) + +#### Example: `async` + +Function and block syntax lines up with existing, stable `async` syntax: + +* `async fn foo() { }` +* `let future = async move { /* ... */ };` + +Trait and closure syntax follows the precedent described in [RFC #3668][]: + +* `async Fn` as the async version of the `Fn` trait +* `async || /* ... */` as the syntax for an async closure. + +Across all examples we see a consistent pattern that `async` appears as a prefix, consistent with its role as a kind of adjective (which in English grammar appears first). + +#### What is new in this RFC + +1. Resolving the unresolved question from [RFC #3668][] in the affirmative (yes, we will use the `async Fn` syntax). +2. Committing to use `async $Trait` as the syntax for any future `async`-colored traits we may add. +3. Committing to use `K $Trait` as the syntax for any other form of `K`-colored traits we may add (e.g., `const`-colored). + +#### What this RFC does NOT specify + +This RFC should NOT be understood as adding any form of `K`-colored traits. For now, the only example of a `K`-colored trait are the `async Fn*` traits introduced by [RFC #3668][]. This RFC does not permit users to name an `async`-colored version of any other trait. It does however specify that *if* we support `async`-colored versions of other trait, they should use the syntax `async $TraitName`. For example, if and when we add support for an async version of the `Drop` trait, it should be named `async Drop`. Furthermore, if and when an RFC emerges from [the ongoing experimentation with const traits](#66792), that RFC should use the syntax `const $Trait`. + +### `K`-colored types + +#### Background: what is a `K`-colored type and why might we want it? + +When you tag a block or function with `async`, it changes its result type to `impl Future`, where `$ty` is the type that would normally have been produced. This characteristic is shared with some other prospective colors (e.g., [`try` and `gen`](#future-possibilities)), but not all: it corresponds to cases where the code executes differently in some way from ordinary code. + +Currently there is no special syntax for this type translation. That is, the only way to reference the "type produced by an async block" is to write an explicit `impl Future`. One example where this comes up frequently is when users wish to write an `async fn` that will execute *some* code eagerly and defer the rest to execute when the future is awaited. The way to do this today is to write a regular function that returns `impl Future`: + +```rust +fn do_something() -> impl Future { + // ... actions take here execute immediately ... + async move { + // ... actions here execute when future is awaited ... + } +} +``` + + +Having users write `impl Future` explicitly in cases like this has downsides. To start, it is verbose. But also, as a new user of Rust, it requires understanding several unnecessary concepts (the trait system, the `Future` trait, `impl Trait` syntax) in order to write async code like this. + +To address these issues, [RFC #3628][] proposes adding a new syntax for "the type that results from an `async` block", but that RFC has not been accepted. Accepting the RFC would require answering two questions: + +1. Does it make sense to add some syntax beyond `impl Future` for the result of an async block? +2. If so, precisely what syntax should we use? + * The RFC proposes `async`, but other syntaxes have been proposed, such as `async -> T`, `impl async -> T`, and `impl Future -> T`. + +#### Conclusion: we do want a syntax, but we don't know what it should be yet + +In discussion the lang team concluded that having an explicit syntax for "the result of an async block" that was tied to the `async` keyword was a good idea. However, similarly to the question about trait syntax, the syntax is most desirable if it is part of a larger pattern. In this case, it is part of two larger patterns: one is using the `async` keyword as a prefix to transform all things related to async (functions, blocks, traits, closures, and now types). The second is that other future color keywords may employ a similar syntax (e.g., [`try`](#try-as-a-color) or [`gen`](#gen-as-a-color)). + +#### What is new in this RFC + +This RFC commits us to adding *some* form of this syntax for `async` but defers the bikeshed for [RFC #3668][] or some future RFC. We refer to this future syntax `🚲async<$ty>`. Usage of this syntax should be equivalent to typing `impl Future`. Note that `🚲async<$ty>` is not a Rust type alias but a syntactic substitution, and therefore the `impl` keyword here has a different role depending on where it appears (in [function arguments][apit], it corresponds to a new generic argument to the fn; in [return position][rpit], it corresonds to an [abstract return type][rpit], etc). + +[apit]: https://doc.rust-lang.org/stable/reference/types/impl-trait.html#anonymous-type-parameters +[rpit]: https://doc.rust-lang.org/stable/reference/types/impl-trait.html#abstract-return-types + +This RFC also recommends that future colors which "transform" the result of a block or function introduce a corresponding syntax. + +Where `🚲K<$ty>` exists it should meet the following invariants: + +1. A `K`-function `K fn foo() -> $ty { $expr }` returns a value of type `🚲K<$ty>` when called. + * The body `$expr` produces/returns a value of the declared type `$ty`. +2. A `K`-block `K { $expr }` produces a result of type `🚲K<$ty>`, where: + * `$ty` is the type produced by the body `$expr` (or `break` statements that target this block). +3. A `K`-closure `K |$args| -> $ty { $expr }` returns a value of type `🚲K<$ty>` when called. + * The body `$expr` produces/returns a value of the declared type `$ty`. + * The `K`-closure implements the trait `K Fn`. +4. There is some operation, called the "do" operation, that takes a `🚲K<$ty>` value and returns a `$ty` value to the surrounding code. + +#### Example: `async` + +* The definition of `🚲async<$ty>` is `impl Future`. +* The "do" operation for `async` is `await`. + +### Relationship between `K`-colored functions, blocks, and types + +Looking at async functions and the proposed `🚲async<$ty>` notation, we observe the following relationship. An `async` function like `count_input`... + +```rust +async fn count_input(urls: &[Url]) -> usize { + // ... iterate over urls and load data ... +} +``` + +...can be transformed into an "ordinary" function that (a) returns `🚲async<$ty>` and (b) uses an `async move` block (caveat: the true transformation is slightly more subtle [in order to preserve the drop order for method arguments][droporder]): + +```rust +fn count_input(urls: &[Url]) -> 🚲async { + async move { + // ... iterate over urls and load data ... + } +} +``` + +This works because async blocks rewrite their content to produce a different value. + +#### What is new in this RFC + +This RFC recommends that future colors that define `🚲K<$ty>` meet this invariant: + +* A `K fn foo() -> T` should be equivalent to a (uncolored) `fn` that returns `🚲async` and uses some form of `K`-colored block. + +## The "filter color" subset + +The previous section defined the color pattern in full with reference to `async`. The `async` color has the effect of rewriting the blocks it is applied to so that the produce a different kind of value. For this reason, we call it a **rewrite color**. The other extant colors in Rust (`unsafe`, `const`) do not have this property. Instead, they are used to track the presence or absence of certain kind of operations. We call these **filter colors**; as we will explain, the full color pattern doesn't make sense for filter colors, so we define the subset that does still apply. This section focuses on `const` specifically; `unsafe` is discussed in the [Future Possibilities](#future-possibilities) section. + +### At first glance, `const` and `async` seem very different + +Both async and const can be applied to blocks and functions, but there are important differences between them. Async changes how a function body is compiled and the type of value that is produced; the result of async functions (and async blocks) are only usable from other async contexts. + +In contrast, `const` limits the operations a function can perform to those that the compiler can execute at runtime. It does not change how a function is compiled at runtime nor does it change the type of value that is produced. + +The relationship between `async` functions and `async` blocks is also different in kind than the relationship between `const` functions and `const` blocks. An `async` function is sugar for a function with an `async` block in its body (and a different return type). In contrast, a `const` function is something that can be run at compilation or runtime, and a `const` block is code that *only* runs at compilation time. + +And yet, despite all these differences, both `const` and `async` intuitively *feel* very similar -- they both *feel* like colors to users, as argued in the motivation. How to understand this? + +### `const` is best understood as an inverted default for a `runtime` color + +The natural orientation for colors is additive: having a `K`-colored block gives access to additional capabilities (like awaiting values). `const`, in contrast, is a *subtractive* color. A `const` function can do fewer things than an ordinary function. To see the difference, imagine an alternate form of Rust, Runtime-Rust. + +"Runtime-Rust" works exactly like Rust, but the defaults are flipped: code blocks, by default, execute at compilation time. To execute at runtime, they must be tagged with `runtime { ... }`, which in turn gives them access to capabilities that are only permitted at runtime, like FFI. This `runtime` color matches `async` much more closely: just as `async` blocks can only fully be used from inside another `async` block, `runtime` blocks can only be used from inside `runtime` blocks. What this thought experiment shows is that `const` is actually an inverted default, like `?Sized`. + +### What parts of the pattern work for `runtime`? + +Continuing with the Runtime-Rust hypothetical, most parts of the color pattern apply equally well to `runtime` and `async`: + +* `K`-colored functions, blocks, and closures are prefixed with the keyword `K`. +* `K`-colored traits are prefixed with the keyword `K`. + * A `K`-colored trait `Trait` include (at least) the members of `Trait`, with some of them colored by `K`. +* `K`-colored functions and closures implement `K`-colored variants of the `Fn` trait. + +There is one part however that doesn't really fit: + +* `K`-colored types can be written with the syntax `🚲K`: + * `K`-colored functions, closures, and blocks return/produce `🚲K` values, where `T` is their original type. + +Introducing a `🚲runtime` syntax to describe a "runtime-colored type" doesn't make sense, because `runtime` blocks don't change the type of value that is produced. So `🚲runtime = T` always. We don't need syntax for this and having it would be confusing. Furthermore, the equivalence between a `K`-function and a function whose body returns a `K`-block doesn't hold: + +* `runtime fn $name() -> T { $expr }` is NOT equivalent to `fn $name() -> T { runtime { $expr } }` + +This equivalence doesn't work because the `runtime` color cannot be *encapsulated*. You can't have a compilation time function that uses a `runtime` block internally but hides it from its callers. In contrast, you *can* have a synchronous Rust function that creates an `async` block internally and executes it by polling in place or some other means. + +### The filter color subset of the color pattern + +Based on our discussion of `runtime`, we can identify the parts of the full color pattern that apply to filter colors: + +* `K`-functions using the syntax `K fn $name() -> $ty`; +* `K`-blocks using the syntax `K { $expr }` (and potentially `K move { $expr }`); +* `K`-traits using the syntax `K $Trait`; + * `K`-colored traits should offer at least the same methods, associated types, and other trait items as an uncolored version of the trait. Some of the items will be `K`-colored, but not necessarily all of them. +* `K`-closures using the syntax `K [move] |$args| $expr` to define a K-closure; + * Closures implement the `K Fn` traits. + +The other parts of the pattern (e.g., the `🚲K` syntax or defining a "do" operation) do not apply because filter colors don't change the type of the value produced by the block. Further, the transformation from `K fn $name` to a function with a `K`-block doesn't work, because the point of a `K fn $name()` is to signal to callers that `$name` can only be called inside of a `K`-block. + +## Teaching async via the "color pattern" + +> The "color pattern" is meant to guide Rust designers as we extend the language, not to be something directly taught to end users. To illustrate how it might feel, this section covers an example of teaching `async` leveraging the syntax and concepts of the color pattern (but not teaching the pattern explicitly). To help illustrate where the overall vision for Rust, we assume that `🚲K<$T>` is `K -> $T` (a variant of [RFC #3628][] currently preferred by the author), that `async -> T` and its equivalent `impl Trait` are supported in local variable declarations ([RFC #2071][]), and that there is some form of `async Iterator` trait available in std. + +### Async functions + +Rust's `async` functions are a built-in feature designed for building concurrent applications. Async functions are just like regular functions, but prefix with the `async` keyword: + +```rust +async fn load_data(input_urls: &[Url]) -> Vec { + let mut data = Vec::new(); + for url in input_urls { + // Load the data from `url` and merge it into `data` + data.push(load_url(url).await); + } + data +} + +async fn load_url(url: &Url) -> Data { + // ... do something ... +} +``` + +When you call an `async` function, the function does not execute immediately. Instead, it returns a suspended function execution, called a *future*. This future can later be *awaited* to cause it to execute. We see this in `load_data`, which invokes a helper async function `load_url` and then awaits the result before pushing it into `data`. + +If we were break that call to `load_url` out into separate statements, it would look like this: + +```rust +for url in input_urls { + // Calling `load_url` yields a future, the type of which is denoted as `async -> Data`: + let url_future: async -> Data = load_url(url); + + // Awaiting the future causes it to execute synchronously. + // Once it completes, we have a `Data`. + let url_data: Data = url_future.await; + + // Load the data from `url` and merge it into `data` + data.push(url_data); +} +``` + +Some things to note: + +* The value returned by `load_url` is not of type `Data`, it's of type `async -> Data`. + This notation indicates that `url_future` is in a fact a future that, when awaited, will yield a `Data` value. +* The notation `url_future.await` is used to await a future. + This causes the current task to block and execute the function. + +In our original example, we immediately awaited the result of the function call (`load_url(url).await`). This is equivalent to calling a synchronous function. + +Where futures become powerful is when you combine them to create concurrent patterns. For example, suppose the caller has a list of URLs `urls` and would like to split it in half and process the data from the two halves concurrently: + +```rust +// Split list of URLs into two pieces: +let mid_point = urls.len() / 2; +let (urls_1, urls_2) = urls.split(mid_point).unwrap(); + +// Create two futures by loading these halves. +// Nothing happens at this point, we just create +// two suspended computations. +let future_1: async -> Vec = load_data(urls_1); +let future_2: async -> Vec = load_data(urls_1); + +// Join those two futures together into a new future. +let future: async -> (Vec, Vec) = join!(future_1, future_2); + +// Await the joined future. This will process both +// halves concurrently and yield up a tuple with +// the two vectors. +let (data_1, data_2): (Vec, Vec) = future.await; +``` + +### Async blocks + +In addition to async functions, Rust supports async *blocks*. These are a lighterweight alternative to defining an async function and are useful when you want to suspend execution of a small piece of code that references various things from its environment, similar to a closure. For example, we might like to make a future that invokes `load_data`, awaits the result, and then does some light pre-processing: + +```rust +let processed_data: async -> ProcessedData = async { + load_data(urls).await.process() +}; +``` + +Creating an async block yields an `async -> T` future representing the suspended computation, just like the futures that result from calling an async function. + +Or, to continue with our `join!` example, we might load and process the data from two sets of URLs concurrently: + +```rust +let (data_1, data_2) = join!( + async { load_data(urls_1).await.process() }, + async { load_data(urls_2).await.process() }, +).await; +``` + +Here, the futures supplied to the `join!` macro are two async blocks, instead of just calls to `load_data`. + +#### `async` vs `async move` + +Async blocks resemble closures in some ways. Just as a Rust closure by default takes references to variables from the surrounding stack frame, async blocks yield futures that store references to the variables they need whenever possible. You can however use the `async move` syntax to force the future to take ownership of any data that is uses. + +### Async closures + +We previous defined `load_data` using a `for` loop: + +```rust +async fn load_data(input_urls: &[Url]) -> Vec { + let mut data = vec![]; + for url in input_urls { + data.push(load_url(url).await); + } + data +} +``` + +We saw in Chapter XX that we rewrite those for-loops to use iterators with a `map`/`collect` call. If you try that in `load_data`, however, it won't compile, because you can't use `await` in a synchronous closure: + +```rust +async fn load_data(input_urls: &[Url]) -> Vec { + input_urls + .iter() + .map(|url| load_url(url).await) + // ^^^^^ + // Error: await from a synchronous closure + .collect() +} +``` + +To use `await` from inside the `map` closure, the closure needs to be tagged as `async`. This in turn requires you to use an *async* iterator, which you can get by invoking `async_iter`. Once you make these changes, we can rewrite `load_data` to use an async iterator as follows: + +```rust +async fn load_data(input_urls: &[Url]) -> Vec { + input_urls + .async_iter() + .map(async |url| load_url(url).await) + .collect() + .await // <-- collect yields a future, must await +} +``` + +> **Meta note:** We are assuming here that some kind of `async Iterator` trait is eventually added that supports functionality similar to [`futures::Stream`](https://docs.rs/futures/latest/futures/prelude/trait.Stream.html). This functionality is not specified in this RFC. + +### Desugaring async functions into async blocks + +The `async fn` syntax is actually a shorthand for a more general form of declaration that makes use of `async -> T` types and `async` blocks. The more general declaration is formed by moving the `async` keyword from before the `fn` to the return type and then transforming the body to use an `async move` block: + +```rust +fn load_data(urls: &[Url]) -> async -> Data { + async move { + // ... same as before ... + } +} +``` + +This more general declaration gives you more control over what happens when `load_data` is called. Among other things, it can let you take some actions right away, while deferring others to run when the resulting future is awaited. To do this, simply add some statements before the `async move` block: + +```rust +fn load_data(urls: &[Url]) -> async -> Data { + // ... this code executes immediately ... + async move { + // ... this code executes when the future is awaited ... + } +} +``` + +### Comparing async-await in Rust to async-await in other languages + +Many languages have some variant of async-await notation and so async functions in Rust likely look familiar to you. That's good! But be aware that async-await works differently in Rust than most other languages. + +The most obvious difference is that where most languages put `await` as a prefix, Rust makes it into a suffix. So instead of writing `await foo` you write `foo.await`. This is more convenient when writing chained operations, like `data.load().await.load_more().await`, and especially when dealing with futures that yield `Result`, as one can use `?` like `load_data().await?`. + +The other, more subtle, difference has to do with Rust's execution model. In most languages, calling an async function implicitly starts up a background task that will continue executing. Rust, like Kotlin, takes a different approach: calling an async function returns a suspended computation, which on its own is inert. That future can then by combined with other futures to form aggregates, like the `join!` operation that we saw in the previous example. Eventually, the future or the aggregate that it is embedded into must be awaited -- and, when it is, that will block your current task until it completes. + +If you'd like the async functon to execute in the background, then you need to use a `spawn` function to create a new task (analogous to spawning a new thread in synchronous code). Rust itself does not provide a spawn function, but one is typically provided by your async runtime. The most common choice here is `tokio`, which offers [`tokio::spawn`](https://docs.rs/tokio/latest/tokio/task/fn.spawn.html): + +```rust +let data_future = tokio::spawn(async move { load_data(urls).await }); +// ... `data` is not being loaded in the background, in parallel ... +``` + +### Digging deeper: the `Future` trait + +We have thus far avoided saying precisely what a future *is*, apart from a "suspended computation". The more precise answer is that a future is a value that implements the `Future` trait. The notation `async` that we have been using is in fact a shorthand for `impl Future`, meaning "a value of some type that implements `Future`". We introduced the `impl Future` notation in Chapter XX, and `async -> T` works the same way: + +* When used in function argument position like `fn method(data: async -> Data)`, `async -> T` is equivalent to adding a new anonymous type parameter `A` where `A: Future`. +* When used in return position like `fn method() -> async -> Data` or in a type alias like `type DataFuture = async -> Data`, `async` desugars to an opaque type whose precise value is inferred from the function body or uses of the type alias, respectively. +* When used in local variable position like `let data: async -> Data = ...`, `async -> Data` desugars to an assertion that the type of `data` implements `Future`. +* In other positions, `async -> T` (like `impl Future`) is an error. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +This section collects the precise statements of this RFC. + +## When is a keyword `K` a color? + +There is no hard and fast rule for this, but the primary criteria to consider are as follows: + +1. The keyword `K` should be applicable to functions and blocks of code. + * `const fn foo()`, `const { .. }` + * `async fn foo()`, `async { .. }`, `async move { .. }` +2. `K`-colored code should generally only be usable with other `K`-colored code. + * `const` functions can only call other `const` functions + * `async` functions return futures that can only be awaited from async blocks + +## Categories of colors + +Colors fall into two, non-exlusive, categories: + +* *Rewrite* colors changes how a `K`-colored block executes, resulting in a distinct type from the block vs an uncolored block. +* *Filter* colors either expand or restrict the set operations that can be performed in a `K`-colored block. + +## The complete color pattern + +Every color keyword `K` supports: + +* `K`-functions using the syntax `K fn $name() -> $ty`; +* `K`-blocks using the syntax `K { $expr }` (and potentially `K move { $expr }`); +* `K`-traits using the syntax `K $Trait`; + * `K`-colored traits should offer at least the same methods, associated types, and other trait items as an uncolored version of the trait. Some of the items will be `K`-colored, but not necessarily all of them. +* `K`-closures using the syntax `K [move] |$args| $expr` to define a K-closure; + * Closures implement the `K Fn` traits. + +Rewrite colors further support: + +* `K`-types using a TBD syntax, denoted `🚲K<$ty>` for now. + * A `K`-type `🚲K<$ty>` is the type resulting from a `K`-block whose expression has type `$ty`. +* A `K`-function `K fn $name($args) -> $ty { $expr }` is "roughly" equivalent to a regular function whose return type and body are `K`-colored: + * `fn $name($args) -> 🚲K<$ty> { K { $expr } }` + * The translation will vary slightly depending on the specifics of the color. The goal is that `K`-functions should behave as close as possible to an uncolored function. For example, drop order should be preserved. +* A `K`-closure returns `🚲K<$ty>`, where `$ty` would have been the return type had the closure been uncolored. +* A "do" operation that, when executed in a `K`-block, consumes a `🚲K<$ty>` and produces a `$ty` value. + +Some colors may only include a subset of these features. This subset should be documented and explained. + +# Rationale, alternatives, and FAQ +[rationale-and-alternatives]: #rationale-and-alternatives + +## Why include `🚲K<$T>` syntax? + +Most parts of the color pattern already exist in Rust today or at least in accepted RFCs. The `🚲K<$T>` syntax for colored types stands out as the exception. It was included in the RFC because it forms an important part of the overall story (witness how prominent it is in the guide section). Some members of the lang team felt that, without `🚲K<$T>`, they didn't feel good about the color pattern overall. + +## Why not use the camel-case named for K-colored traits, like `AsyncFn` instead of `async Fn`? + +We considered a number of alternatives to `K Trait` for denoting K-colored traits. Perhaps the most obvious was a camelcased identifier like `AsyncFn`. While this is a perfectly viable option, it has downsides as well: + +* First, the story of how to transition something from sync to async gets more complicated. It's not a story of "just add `async` keywords in the right places". +* Second, this convention does not offer an obvious way to support const traits like `const Default`, unless we are going to produce `ConstDefault` variants as well somehow. And if there are more variants in the future, e.g., `AsyncSendSomeTrait` or `AsyncTrySendSomeTrait`, it becomes very unwieldy. +* Third, although we are not committing to any form of "color generics" in this RFC, we would also prefer not to close the door entirely; using an entirely distinct identifier like `AsyncFn` would make it very difficult to imagine how one function definition could be made generic over a color. + +Ultimately, the killer argument was that this felt like everybody's preferred "second choice option", but nobody's *favorite*. It's not a bold design. + +## Why not use a notation like `T: async fn()` instead of `T: async Fn()`? + +One advantage of today's sugar for trait bounds (`T: Fn()`, `T: FnMut()`, etc) is that it more closely resembles function declarations. Adding the async keyword continues that, in that one now puts `async` in front of the `fn` or closure and then likewise in front of the bound (`T: async Fn()`). Iterating on that vein, we considered going further with bound notation, so that one would write `T: fn()` instead of `T: Fn()` and then `T: async fn()` instead of `T: async Fn()`. The following objections were raised: + +* There is no obvious way to encode `T: FnMut()` or `FnOnce()`. Should the notation be `T: fn mut()`and `T: fn once()`? Then we are losing the parallel to function declarations and introducing some ad-hoc keywords. +* Changing the notation for `T: Fn()` bounds is a massive change affects virtually all existing Rust code. To make a change like that, we have to be very confident the change will be a win, and many were not (particularly given the previous bullet). + +## How did you come up with "rewrite" color and " + +## Can `🚲K<$T>` be used in closure return types like it can for regular functions? + +The answer to this is complicated and left to be resolved by future RFCs that actually add the `🚲K<$T>` notation. Recall that we specified that `K`-colors can be migrated from a `K`-function to its return type and body: + +```rust +K fn foo() -> T { ... } +// becomes +fn foo() -> 🚲K { K { ... } } +``` + +The question then is whether it is possible to transform a a `K`-closure in a similar way: + +```rust +K |args| -> T { ... } +// could maybe become +|args| -> 🚲K { K { ... } } +``` + +Supporting this notation is tricky however because closure return types are often elided. Given that `K`-closure types implement `K Fn` rather than the typical `Fn` trait, we need to be able to determine if a closure has color `K` to decide what traits it implements. But there is no way to distinguish whether an expression like `|| K { .. }` is meant to be a `K`-closure that implements `K Fn` or an uncolored closure that implements `Fn>` value. As described in [RFC #3668][], these two traits are not equivalent for `async` and likely not for other future rewrite colors. + +We've left the behavior here to be specified in a future RFC, but we note that it would be simple to declare it as an error for now. Note that `-> impl Future` is also not accepted in this position. + +# Prior art +[prior-art]: #prior-art + +The term "color" is taken from the ["What color is your function?"][WCIF] blog post. As that post indicates, many languages have added colors of one form or another. This RFC is focused on the syntactic patterns around colors, specifically using the color as a prefix (e.g., `async fn`, `🚲async`, `async Trait`). This pattern of prefixing functions with colors is very common and is very natural in English as the color plays the role of an adjective, describing the kind of function one has. + +Although the RFC is focused on syntax, it does also suggest some equivalences that colors must maintain, such as how a `K fn foo() -> T { expr }` can be transformed to `fn foo() -> 🚲K { K move { expr } }` for any rewrite color `K`. These equivalences hint at an underlying semantics for colors. There are two related precedents in academia: + +* [Haskell]'s monads are comparable in some ways to rewrite colors, and the [monadic laws][] suggest similar equivalences. A "[monad]" is like a color made up of two operations, one that "wraps" a value `v` to yield a `Wrapped` (analogous to `K-type!(v)`) and one that does an `and_then` operation, often called "fold", taking a `Wrapped`and a `V => Wrapped` closure and yielding a `Wrapped` value. The effect is kind of like a "programmable semicolon", allowing [Haskell] programs to introduce operations in in between each statement, and to potentially carry along "user data" with the wrapped `V` value. Haskell includes generic syntax ("do"-notation) designed to work with monads, allowing one to define generic functions that can be used with any monad `Wrapped`. + * Monads are famously challenging to learn ("The sheer number of different monad tutorials on the internet is a good indication of the difficulty many people have understanding the concept", says the Haskell wiki). Colors run the risk of being similarly complicated. However, this RFC is not introducing colors as a concept that users would be taught, but rather as a syntactic pattern that they will pick up on as they learn about different existing parts of Rust (async, unsafe, etc). + * Ergonomic challenges with monads often arise when composing or converting between monads. These same problems arise with colors (e.g., how to call an async function from a sync function), but those problems already exist and are not made worse by this RFC.) +* Effects in languages like [Koka][] can be compared to colors. Effects have evolved over the years from a way of signalling side effects (from which the name derived) to an expressive construct for injecting new operations into functions. An effect in [Koka][] is a function that is threaded down through context, the usage of which must be declared. Because [Koka][] is based on [continuation passing style][cps], effect functions are able to abort computation (modeling exceptions) or pause and resume it (modeling generators). + +[Koka]: https://koka-lang.github.io/koka/doc/index.html +[Haskell]: https://www.haskell.org/ +[monadic laws]: https://wiki.haskell.org/Monad_laws +[monad]: https://wiki.haskell.org/All_About_Monads +[cps]: https://en.wikipedia.org/wiki/Continuation-passing_style + + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +## What syntax to use for `🚲K<$T>`? + +This question is purposefully left unresolved by this RFC and is meant to be addressed in follow-up RFCs, such as [RFC #3628][]. + +# Future possibilities +[future-possibilities]: #future-possibilities + +## Expanding on our existing colors + +This RFC suggests a number of future changes to "fill out" the support for colors: + +* Async should add a `🚲async` syntax (see [RFC #3628][]). +* We will eventually need a mechanism for defining async-colored traits like `async Iterator` or `async Default`. For the short term this is not urgent as the traits we are focused on (such as `Fn` and `Drop`) are special. +* Const-colored traits should use a notation like (e.g.) `const Default`. +* Unsafe-colored traits like `unsafe Default`, which would mean a `Default` trait with an unsafe `default` method, are a possibility, but they could be easily confused for an unsafe trait. + +## More complex colors + +In authoring this RFC, we also explored what future colors might look like. Three potential future colors are `unsafe`, `try` (for fallible functions that yield a `Result`, most commonly at least) and `gen` (for generators). `try` and `gen` would be rewrite colors. + +Unlike `async` and `const`, these three keywords are not themselves *colors* but more like *families of colors*. `unsafe` for example indicates that the function has safety predicates that must be proven before it can be called and hence the "true color" conceptually includes those predicates (though we don't write them explicitly in our notation). `try` and `gen` both have other types associated with them beyond the main output type. + +### `unsafe` as a filter color + +`unsafe` is a filter color. The `unsafe` color allows operations that are not proven safe using the Rust type system and which therefore require user validation. In contrast to some filter colors, `unsafe` doesn't actually change the set of operations that a given piece of code can perform *at runtime* -- a safe block can invoke a safe functon which includes an unsafe block (and most do), so safe blocks can perform the same actions as unsafe blocks. Furthermore the `unsafe` color, despite being 1 keyword, is really a "set" of colors, in that each `unsafe fn` has its own safety requirements that are distinct from one another. + +Treating `unsafe` as a filter color would mean adding the following pieces of syntax: + +* an `unsafe $TraitName` syntax, where some operations in `$TraitName` unsafe; +* unsafe closures `|$args| $expr` that implement `unsafe Fn`. + +This would close an existing "wart" in the language: a safe `fn foo()` implements the `Fn()` trait, but an `unsafe fn foo()` does not (because the "call" method in `Fn` is safe). Similarly, the type `fn()` of safe function pointers implements `Fn`, but the type `unsafe fn()` of unsafe function pointers does not. + +There is a complication to considering `unsafe` as a color because we already have a notion of an `unsafe` trait, with a distinct meaning. An unsafe trait is one where implementors prove a safety predicate relied on by the caller, as opposed to an `unsafe`-colored trait, which is a trait where the caller proves a safety predicate relied upon by the impl. + +It's also worth noting the `unsafe_op_in_unsafe_fn` lint, which encourages the use of `unsafe` blocks even within an `unsafe` function. This is likely to be different from other effect-carrying colors that could be added (e.g., perhaps one that tracks whether allocation or panics can occur in a function). This difference stems in part from the fact that `unsafe` is not tracking an "effect" that occurs at runtime but rather an aid to static reasoning. + +### `try` as a rewrite color + +The `try` keyword was introduced in [RFC #243][], along with the `?` operator. It has been unstable since [RFC #243][] was merged in 2016 and has seen significant evolution. + +As described in [RFC #243][], a `try { $expr }` block executes `$expr` and "captures" any `?` operations that occur within. On successful completion, the result of the try block is not the result of `$expr` but rather an "ok-wrapped" variant. The term "ok wrapping" refers to the common case where the `try` block produces a `Result`; the idea is that `try { 22 }` would be equivalent to `Ok(22)`. During the course of `try`'s long history, the design [temporarily changed *not* to `Ok`-wrap][#41414]. However, [the original design was ultimately restored][#70941]. + +[#41414]: https://github.com/rust-lang/rust/issues/41414 +[#70941]: https://github.com/rust-lang/rust/issues/70941 + +Looking at `try` as a color, it is clear that ok-wrapping is the correct and consistent behavior. `try`, like `async`, is a fully general color that transforms its result type, and the color pattern specifies that the type of an expression in a block should be "ok-wrapped" to produce the block's final output type. Another point of controversy about `try` has been how to support it syntactically at the function level. This RFC suggests that the right answer is `try fn foo() -> T`, where `T` represents the "ok" type. The final result of calling `foo()` would therefore be `🚲try`, the ok-wrapped version of `T`. + +While treating `try` as a color is appealing, it does have one important difference from `async`: `try` and `?` are intended to be usable with many types, including `Result`, `Option`, and others. In terms of the color pattern, it is therefore unclear what the syntax `🚲try` should expand to. + +[`Try`]: https://doc.rust-lang.org/std/ops/trait.Try.html + +One possibility is permitting the user to explicitly declare (and important) `try` colors themselves in the form of a type alias. For example, the `std::io::Result` pattern might instead be: + +```rust +// Older alias, deprecated: +type Result = Result; + +// in std::io, we define `try` as an alias to transformed type, +// given the Ok type: +type try = Result; +``` + +Users could then import `try` from `std::io` and use it: + +```rust +use std::io::try; + +try fn load_configuration_string() -> String { + std::fs::read_to_string("config.txt")? +} +``` + +This would be equivalent to the following uncolored function returning `🚲try`: + +```rust +use std::io::try; + +fn load_configuration_string() -> 🚲try { + try { std::fs::read_to_string("config.txt")? } +} +``` + +This is only one possibility. There are several other ideas for how try could be integrated: + +* Define `type try = Result` as an alias for the residual and use `try -> T` to mean `impl Try` (this assumes `🚲try` is defined as `try -> T`). +* Extend `try fn` with a clause like `try fn foo() -> T throws X` and have it expand to `impl Try` or something like that. + +### `gen` as a rewrite color + +The `gen` keyword has been proposed for introducing generator blocks, which are a syntactic pattern to make it easier to write iterators (and perhaps to fill other use cases, there is a range of design space still to be covered). One notation proposed for `gen` is `gen`, which appears at first glance to resemble the proposed `async` type notation from [RFC #3628]. However, as proposed, `gen` is actually quite different, as the `T` here represents the type of value that is yielded by the iterator. Therefore the generator may produce any number of `T` instances, whereas `async` produces exactly one `T` instance. This means that they are used very differently by users, and it is unclear whether what parts of the color pattern should apply to `gen` in this case. + +That said, there is some precedent for treating "generators" as color-*like* things. In Haskell, the `List` monad performs implicit `flat_map` operations, meaning that a given piece of code may execute many times. In Rust terms, this would correspond to nested `for` loops. Consider the following Rust-like pseudocode: + +```rust +gen fn even() yields u32 { + // In Haskell-like pseudocode, this might be something like + // + // do + // i <- all() + // if i % 2 == 0 { return i } + // + // Here, the fact that there is a for-loop is "hidden" in the + // monad itself, and not apparent in the `do`. + // + // In contrast, under the proposed gen designs, the `for` loop + // in Rust code would be explicit, not something that happens + // via an implicit mechanism: + for i in all() { + if i % 2 == 0 { + yield i; + } + } +} + +gen fn all() yields u32 { + for i in 0.. { + yield i; + } +} +``` + +Treating `gen` as a `flat_map` operation does not map to the "do" operation defined in the color pattern. The "do" operation is meant to take a transformed value of type `🚲gen` and produce a single value of type `T`; but `flat_map` produces any number of `T` values. This is not necessarily a problem, not all colors have to provide all parts of the pattern, but it would suggest that `gen` is a distinct category of color from a rewrite color like `async` or `try`. + +There is however another "do" operation we might use for generators which *does* fit the rewrite pattern, but which may not be as intuitive or useful to end users. In addition to the yield type that we have been discussing, generators can be extended with the idea of a final value type; we can use the syntax `🚲gen`, where `Y` is the *yielding* type and `F` is the *final* type that is returned. (An existing `impl Iterator` can be seen as having a yield type of `I` and a final type of `()`.) In this case, the "do" operation would be `yield_all`, which would yield up all `Y` items and then return the final `F` value. + +Here is an example using the hypothesized "yield all" construct, showing how you could work with `gen` as a color. In this case, the `yield_all` 'unwraps' the generator effect, propagating the yielded values, and returning the final value. Calling `outer` would then yield up [`0`, `1`, `2`, `6`, `7`, `8`] and return 42. Note how `.yield_all` appears at the same places that `.await` would appear if these were async functions: + +```rust +// Example of `gen` as a color. +// +// Clearly this syntax is suboptimal, it is only +// meant to illustrate the concept. + +gen fn outer() -> u32 { + // Yields 0, 1, 2 and returns 6 = (0 + 1 + 2) * 2 + let mid = starting_at(0).yield_all; + + // Yields 6, 7, 8 and returns 42 = (6 + 7 + 8) * 2 + starting_at(mid).yield_all +} + +gen fn starting_at(x: u32) -> u32 { + let mut sum = 0; + for i in x .. x+3 { + sum = sum + i; + yield i; + } + sum * 2 +} +``` + +Obviously this is a syntax that only a mother could love (it's a techical term). An alternative syntax might move the `Yielding =` part `gen` to a keyword or separate part of the declaration: + +```rust +// In place of this... +gen fn outer() -> u32 {} + +// ...maybe this? +gen fn outer() -> u32 yielding u32 {} + +// ...or this? +gen fn outer() -> u32, Yielding = u32 {} +``` + +Clearly more exploration is needed here. The best option may be to say that `gen` does not follow the rewrite color pattern in its full particulars. \ No newline at end of file From 829f1b40af64860afa73f120a1d879117e9ecf71 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 10 Oct 2024 12:58:23 -0400 Subject: [PATCH 02/11] whoops, provide missing answer --- text/0000-color-syntactic-design-pattern.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/0000-color-syntactic-design-pattern.md b/text/0000-color-syntactic-design-pattern.md index a6d4f51b115..0e2040ff76f 100644 --- a/text/0000-color-syntactic-design-pattern.md +++ b/text/0000-color-syntactic-design-pattern.md @@ -547,7 +547,9 @@ One advantage of today's sugar for trait bounds (`T: Fn()`, `T: FnMut()`, etc) i * There is no obvious way to encode `T: FnMut()` or `FnOnce()`. Should the notation be `T: fn mut()`and `T: fn once()`? Then we are losing the parallel to function declarations and introducing some ad-hoc keywords. * Changing the notation for `T: Fn()` bounds is a massive change affects virtually all existing Rust code. To make a change like that, we have to be very confident the change will be a win, and many were not (particularly given the previous bullet). -## How did you come up with "rewrite" color and " +## How did you come up with "rewrite" color and "filter" color? + +We deliberately chose non-standard terms to avoid associations. For example, the "filter" color might also be called an "effect-carrying color", but that invites debate as to the precise definition of an effect and whether `unsafe` qualifies. ## Can `🚲K<$T>` be used in closure return types like it can for regular functions? From 592b54e49607b2bd07ca5ed6871b73cd141be309 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 10 Oct 2024 13:12:21 -0400 Subject: [PATCH 03/11] add two more FAQ --- text/0000-color-syntactic-design-pattern.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/text/0000-color-syntactic-design-pattern.md b/text/0000-color-syntactic-design-pattern.md index 0e2040ff76f..223833f8140 100644 --- a/text/0000-color-syntactic-design-pattern.md +++ b/text/0000-color-syntactic-design-pattern.md @@ -526,6 +526,20 @@ Some colors may only include a subset of these features. This subset should be d # Rationale, alternatives, and FAQ [rationale-and-alternatives]: #rationale-and-alternatives +## So...what does it ACTUALLY MEAN to say that a keyword `K` "is a color"? + +Answer: nothing. It's just an observation of fact! `K` is a keyword that can be applied to blocks and functions and which affects how code can interoperate. + +Longer answer: it suggests that `K` should support the syntaxes and operations described in this RFC. Each color is different, so it is possible that a given piece of syntax is not relevant, but you ought to be able to explain why. Certainly past experience with colors suggests that over time you will want to be able to use them in all the places identified in the color pattern to allow people to write code naturally.g + +## What about `mut`, is that a color? + +The `mut` keyword does not qualify as a color per the definition in this RFC because it cannot be applied to functions or blocks. As such, it's not clear how one would extend `mut` to apply to other locations like traits or closures. + +However, it *is* true that `mut` is part of what often feels like "3 modes" in Rust: `self`, `&self`, and `&mut self`. It is possible to imagine creating some sort of color such that e.g. the `Fn`, `FnMut`, and `FnOnce` traits, for example, would be colored variants of a single "callable" trait. + +Another similar split is `Send` vs not `Send`. We don't have a keyword for this, but especially in async code it is a very real split. + ## Why include `🚲K<$T>` syntax? Most parts of the color pattern already exist in Rust today or at least in accepted RFCs. The `🚲K<$T>` syntax for colored types stands out as the exception. It was included in the RFC because it forms an important part of the overall story (witness how prominent it is in the guide section). Some members of the lang team felt that, without `🚲K<$T>`, they didn't feel good about the color pattern overall. @@ -760,4 +774,4 @@ gen fn outer() -> u32 yielding u32 {} gen fn outer() -> u32, Yielding = u32 {} ``` -Clearly more exploration is needed here. The best option may be to say that `gen` does not follow the rewrite color pattern in its full particulars. \ No newline at end of file +Clearly more exploration is needed here. The best option may be to say that `gen` does not follow the rewrite color pattern in its full particulars. From 54256fa25348d9cbb90d58ff3864197ad8fe87c3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 10 Oct 2024 13:23:27 -0400 Subject: [PATCH 04/11] add "why not effects" to FAQ --- text/0000-color-syntactic-design-pattern.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/text/0000-color-syntactic-design-pattern.md b/text/0000-color-syntactic-design-pattern.md index 223833f8140..cdd380739be 100644 --- a/text/0000-color-syntactic-design-pattern.md +++ b/text/0000-color-syntactic-design-pattern.md @@ -540,6 +540,24 @@ However, it *is* true that `mut` is part of what often feels like "3 modes" in R Another similar split is `Send` vs not `Send`. We don't have a keyword for this, but especially in async code it is a very real split. +## Why did you use the word "color" and not "effect"? + +clarfonthey [asked](https://github.com/rust-lang/rfcs/pull/3710#issuecomment-2405647443): + +> So, I understand why you're referring to these as colours, since "coloured functions" is what the larger programming language community uses as a term, but I think that the naming of effects should be used instead, especially since that's at least what the current Rust WGs have been settling on. + +I avoided the term "effect" for a few reasons. + +One of them is that I think that it is overall kind of jargon. What's more, my observation is that it is *divisive* jargon, as people bring pre-conceived notions of what it *ought* to mean, and not everything that I consider a "color" fits into those notions. + +My take on an *effect* is that it is some kind of "operation" that occurs during execution, such as a write to a specific memory region, a panic, a memory allocation, etc. It's reasonable to model this kind of effect as a "function" you can call when that event occurs (perhaps with some arguments). + +From what I can tell, this definition lines up with Koka (which is a very cool language). However, Koka is also (I believe) based on Continuation Passing Style, which means that simple function calls get a lot more power. This allows them to model e.g. generators or exceptions as effects. + +To my mind, this is kind of cheating, or at least *misleading*. In particular, we can't "just" port over Koka's abstractions to Rust because we also have to account for rewrites. + +In any case, I'm open to a terminology discussion, though *personally* I'd be inclined not to rename colors to effects, but perhaps to rename *filter colors* to *effect* colors or *effect-carrying* colors. + ## Why include `🚲K<$T>` syntax? Most parts of the color pattern already exist in Rust today or at least in accepted RFCs. The `🚲K<$T>` syntax for colored types stands out as the exception. It was included in the RFC because it forms an important part of the overall story (witness how prominent it is in the guide section). Some members of the lang team felt that, without `🚲K<$T>`, they didn't feel good about the color pattern overall. From ee2b1cc41dc63b6b81bf68cd13c5e3f884710dc1 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 15 Oct 2024 14:50:19 -0400 Subject: [PATCH 05/11] adopt the term "flavor" over "color" --- text/0000-color-syntactic-design-pattern.md | 283 ++++++++++---------- 1 file changed, 138 insertions(+), 145 deletions(-) diff --git a/text/0000-color-syntactic-design-pattern.md b/text/0000-color-syntactic-design-pattern.md index cdd380739be..815bcd7a88f 100644 --- a/text/0000-color-syntactic-design-pattern.md +++ b/text/0000-color-syntactic-design-pattern.md @@ -1,9 +1,7 @@ ---- -title: 'RFC: The "color" syntactic design pattern' +- Start Date: 2024-10-15 +- RFC PR: [rust-lang/rfcs#3710](https://github.com/rust-lang/rfcs/pull/3710) ---- - -# RFC: The "color" syntactic design pattern +# RFC: The "flavor" syntactic design pattern [#67792]: https://github.com/rust-lang/rust/issues/67792 [RFC #3628]: https://github.com/rust-lang/rfcs/pull/3628 @@ -12,49 +10,49 @@ title: 'RFC: The "color" syntactic design pattern' [RFC #2071]: https://github.com/rust-lang/rfcs/blob/master/text/2071-impl-trait-existential-types.md [asyncfg]: https://rust-lang.github.io/rust-project-goals/2024h2/async.html [droporder]: https://github.com/rust-lang/rust/blob/0b16baa570d26224612ea27f76d68e4c6ca135cc/compiler/rustc_ast_lowering/src/item.rs#L1179-L1210 -[WCIF]: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ +[WCIF]: https://journal.stuffwithstuff.com/2015/02/01/what-flavor-is-your-function/ # Summary [summary]: #summary -This RFC unblock the stabilization of async closures by committing to `K $Trait` (where `K` is some keyword like `async` or `const`) as a pattern that we will use going forward to define a "K-variant of `Trait`". This commitment is made as part of committing to a larger *syntactic design pattern* called **the color pattern**. The color pattern is "advisory". It details all the parts that a "color-like keyword" should have and suggests specific syntax that should be used, but it is not itself a language feature. +This RFC unblock the stabilization of async closures by committing to `K $Trait` (where `K` is some keyword like `async` or `const`) as a pattern that we will use going forward to define a "K-variant of `Trait`". This commitment is made as part of committing to a larger *syntactic design pattern* called **the flavor pattern**. The flavor pattern is "advisory". It details all the parts that a "flavor-like keyword" should have and suggests specific syntax that should be used, but it is not itself a language feature. -In the color pattern, each color is tied to a specific keyword `K`. Colors share the "infectious property": code with color `K` interacts naturally with other code with color `K` but only interacts in limited ways with code without the color `K`. Every color keyword `K` should support at least the following: +In the flavor pattern, each flavor is tied to a specific keyword `K`. Flavors share the "infectious property": code with flavor `K` interacts naturally with other code with flavor `K` but only interacts in limited ways with code without the flavor `K`. Every flavor keyword `K` should support at least the following: * `K`-functions using the syntax `K fn $name() -> $ty`; * `K`-blocks using the syntax `K { $expr }` (and potentially `K move { $expr }`); * `K`-traits using the syntax `K $Trait`; - * `K`-colored traits should offer at least the same methods, associated types, and other trait items as an uncolored version of the trait. Some of the items will be `K`-colored, but not necessarily all of them. + * `K`-flavored traits should offer at least the same methods, associated types, and other trait items as the unflavored trait (there may be additional items specific to the `K`-flavor). Some of the items will be `K`-flavored, but not necessarily all of them. * `K`-closures using the syntax `K [move] |$args| $expr` to define a K-closure; * Closures implement the `K Fn` traits. -Some colors rewrite code so that it executes differently (e.g., `async`). These are called **rewrite colors**. Each such color should have the following: +Some flavors rewrite code so that it executes differently (e.g., `async`). These are called **rewrite flavors**. Each such flavor should have the following: * A syntax `🚲K<$ty>` defining the `K`-type, the type that results from a `K`-block, `K`-function, or `K`-closure whose body has type `$ty`. * The `🚲K<$ty>` is a placeholder. We expect a future RFC to define the actual syntax. * A "do" operation that, when executed in a `K`-block, consumes a `🚲K<$ty>` and produces a `$ty` value. -* The property that a `K`-function can be transformed to a regular function with a `K`-colored return type and body. +* The property that a `K`-function can be transformed to a regular function with a `K`-flavored return type and body. * i.e., the following are roughly equivalent (the precise translation can vary so as to e.g. preserve drop order): * `K fn $name($args) -> $ty { $expr }` * `fn $name($args) -> 🚲K<$ty> { K { $expr } }` ## Binding recommendations -Existing color-like keywords in the language do not have all of these parts. The RFC therefore includes a limited set of binding recommendations that brings them closer to conformance: +Existing flavor-like keywords in the language do not have all of these parts. The RFC therefore includes a limited set of binding recommendations that brings them closer to conformance: -* Commit to `K $Trait` as the syntax for applying colors to traits, with the `async Fn`, `async FnMut`, and `async FnOnce` traits being the only current usable example. +* Commit to `K $Trait` as the syntax for applying flavors to traits, with the `async Fn`, `async FnMut`, and `async FnOnce` traits being the only current usable example. * Commit to adding a TBD syntax `🚲async<$ty>` that will meet the equivalences described in this RFC. ## Not part of this RFC -The [Future Possibilities](#future-possibilities) discusses other changes we could make to make existing and planned colors fit the pattern better. Examples of things that this RFC does NOT specify (but which early readers thought it might): +The [Future Possibilities](#future-possibilities) discusses other changes we could make to make existing and planned flavors fit the pattern better. Examples of things that this RFC does NOT specify (but which early readers thought it might): -* Any form of "effect" or "color" generics: - * Colors in this RFC are a pattern for Rust designers to keep in mind as we explore possible language features, not a first-class language feature; this RFC also does not close the door on making them a first-class feature in the future. +* Any form of "effect" or "flavor" generics: + * Flavors in this RFC are a pattern for Rust designers to keep in mind as we explore possible language features, not a first-class language feature; this RFC also does not close the door on making them a first-class feature in the future. * Whether or how `async` can be used with traits beyond the `Fn` traits: - * For example, the RFC specifies that **if** we add an async version of the `Read` trait, it will be referred to as `async Read`, but the RFC does **not** specify whether to add such a trait nor how such a trait would be defined or what its contents would be. + * For example, the RFC specifies that **if** we add an async flavor of the `Read` trait, it will be referred to as `async Read`, but the RFC does **not** specify whether to add such a trait nor how such a trait would be defined or what its contents would be. * How `const Trait` ought to work ([under active exploration][#67792]): - * The RFC only specifies that the syntax for naming a `const`-colored trait should be `const Trait`; it does not specify what a `const`-colored trait would mean or when that syntax can be used. + * The RFC only specifies that the syntax for naming a `const`-flavored trait should be `const Trait`; it does not specify what a `const`-flavored trait would mean or when that syntax can be used. * What specific syntax we should use for `🚲K<$ty>`: * We are committed to adding this syntax at least for `async`, but the precise syntax still needs to be pinned down. [RFC #3628][] contains one possibility. @@ -65,19 +63,17 @@ The Rust Project has a [flagship goal][asyncfg] of bringing the async Rust exper ## Consensus: `async Fn` is nice, but only as part of a larger design pattern -The lang team discussed the syntax question [in a design meeting][DM] and concluded that we prefer offering the `async Fn` syntax as a means of requesting the "async version of a trait", but we would want it to be part of a consistent **syntactic design pattern** for selecting variants of traits. +The lang team discussed the syntax question [in a design meeting][DM] and concluded that we prefer offering the `async Fn` syntax as a means of requesting the "async flavor of a trait", but we would want it to be part of a consistent **syntactic design pattern** for selecting variants of traits. [DM]: https://hackmd.io/@rust-lang-team/rJxAOyWaC -In other words, if users wrote `async Fn` to get an async version of a closure but `AsyncRead` to get the async version of the `Read` trait, that would be confusing. In contrast, if the pattern to get the async version of a trait is always to write the `async` keyword (e.g., `async Fn` and `async Read`), that is appealing. This is true even if the `async Read` trait is defined in a very separate way from its sync counterpart. - -We also observed that there is a need for having "variants of traits" in other similar contexts, most notably `const`, where there is [active experimentation for a const-trait design][#67792]. If we further extend the pattern to cover those cases, so that one writes (e.g.) `const Fn` or `const Default` to the "const versions of the `Fn` or `Default` traits" respectively, then these two instances of the same pattern reinforce one another, helping to create a coherent whole. Any future instances we add would only strenghten this effect. +In other words, if users wrote `async Fn` to get an async flavor of a closure but `AsyncRead` to get the async flavor of the `Read` trait, that would be confusing. In contrast, if the pattern to get the async flavor of a trait is always to write the `async` keyword (e.g., `async Fn` and `async Read`), that is appealing. This is true even if the `async Read` trait is defined in a very separate way from its sync counterpart. -## A latent set of "colors" exists in Rust today +We also observed that there is a need for having "variants of traits" in other similar contexts, most notably `const`, where there is [active experimentation for a const-trait design][#67792]. If we further extend the pattern to cover those cases, so that one writes (e.g.) `const Fn` or `const Default` to the "const flavors of the `Fn` or `Default` traits" respectively, then these two instances of the same pattern reinforce one another, helping to create a coherent whole. Any future instances we add would only strenghten this effect. -Looking more closely at `async` and `const`, we see that there is a latent concept within Rust that we refer to as **colors**. The term color is a playful reference to the famous ["What color is your function?"][WCIF] blog post. +## A latent set of "flavors" exists in Rust today -Each color has an associated keyword `K` that can be applied to functions and blocks. Code with the color `K` interoperates fully with other code with the same color; working across colors is generally more limited: +Looking more closely at `async` and `const`, we see that there is a latent concept within Rust that we refer to as **flavors**. Each flavor has an associated keyword `K` that can be applied to functions and blocks. Code with flavor `K` interoperates fully with other code with the same flavor; working across flavors is generally more limited: * `async` functions return an `impl Future` which can only be `await`'d from another `async` block. * `unsafe` functions can only be called from `unsafe` blocks or other `unsafe` functions. @@ -85,45 +81,45 @@ Each color has an associated keyword `K` that can be applied to functions and bl ## Tenet: consistent syntax and transformations makes Rust easier to learn -The premise of this RFC is that color keywords like `const`, `async`, and `unsafe` "feel similar" to users and that we should make them work as consistently as possible (but no more than that). Because they serve different purposes, we don't wish to force them into a single shape if that shape is an ill-fit. Nonetheless, we wish to ensure that these colors, and any future "color-like" features, feel as consistent and predictable as possible, so that users can develop "muscle memory" that helps them to predict syntax they haven't used or seen yet. The RFC describes the syntax to use for a color keyword applied to various constructs as well as some of the "approximate equivalences" that colors should generally hold. +The premise of this RFC is that flavor keywords like `const`, `async`, and `unsafe` "feel similar" to users and that we should make them work as consistently as possible (but no more than that). Because they serve different purposes, we don't wish to force them into a single shape if that shape is an ill-fit. Nonetheless, we wish to ensure that these flavors, and any future "flavor-like" features, feel as consistent and predictable as possible, so that users can develop "muscle memory" that helps them to predict syntax they haven't used or seen yet. The RFC describes the syntax to use for a flavor keyword applied to various constructs as well as some of the "approximate equivalences" that flavors should generally hold. ## Role of this RFC: identify and describe this pattern -Code with a given color `K` is meant to interoperate fully with other code with the same color, but in practice that is not the case. This is precisely the gaps we are aiming to close by considering features like [async closures][RFC #3668] or [const traits][#67792]. The role of this RFC is to identify the "color pattern" and carry it to its logical conclusion. +Code with a given flavor `K` is meant to interoperate fully with other code with the same flavor, but in practice that is not the case. This is precisely the gaps we are aiming to close by considering features like [async closures][RFC #3668] or [const traits][#67792]. The role of this RFC is to identify the "flavor pattern" and carry it to its logical conclusion. -Existing colors do not yet support all the aspects described herein. This RFC recommends changes that close a limited number of these gaps; the [Future Possibilities](#future-possibilities) discusses other changes we could make to more fully support the color pattern for every color. +Existing flavors do not yet support all the aspects described herein. This RFC recommends changes that close a limited number of these gaps; the [Future Possibilities](#future-possibilities) discusses other changes we could make to more fully support the flavor pattern for every flavor. -## Tenet: all parts of the color pattern should have explicit syntax centered on the keyword `K` +## Tenet: all parts of the flavor pattern should have explicit syntax centered on the keyword `K` -One of the recommendations of this RFC is committing to add a `K`-colored type syntax in the future, similar to what is proposed in [RFC #3628][]. The motivation for this is that it is important when teaching colors to be able to teach the *color specifically*, without having to reference additional language features like `impl Trait`. This addresses feedback we have received from Rust trainers which is that one of the difficulties in teaching Rust is that users can't learn Rust in "layers", they have to learn all parts of Rust at once before any of it makes sense. +One of the recommendations of this RFC is committing to add a `K`-flavored type syntax in the future, similar to what is proposed in [RFC #3628][]. The motivation for this is that it is important when teaching flavors to be able to teach the *flavor specifically*, without having to reference additional language features like `impl Trait`. This addresses feedback we have received from Rust trainers which is that one of the difficulties in teaching Rust is that users can't learn Rust in "layers", they have to learn all parts of Rust at once before any of it makes sense. ## Tenet: no false equivalence, but partial consistency is better than none -Just because colors share many elements doesn't make them equivalent. We also describe how the color pattern applies to `const` and show why some parts of the pattern don't make sense in that context. We think that `const` should use the pattern where it makes sense, but avoid it (or use a distinct pattern) where it does not fit. +Just because flavors share many elements doesn't make them equivalent. We also describe how the flavor pattern applies to `const` and show why some parts of the pattern don't make sense in that context. We think that `const` should use the pattern where it makes sense, but avoid it (or use a distinct pattern) where it does not fit. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Our guide-level explanation proceeds in three phases. We begin by **defining the color pattern** for **rewrite colors**, using `async` as our running example. A *rewrite color* `K` is one that, like `async`, transforms the result type for `K`-colored functions and blocks into a distinct type, reflecting the impact of the color. +Our guide-level explanation proceeds in three phases. We begin by **defining the flavor pattern** for **rewrite flavors**, using `async` as our running example. A *rewrite flavor* `K` is one that, like `async`, transforms the result type for `K`-flavored functions and blocks into a distinct type, reflecting the impact of the flavor. -Next, we define a **subset of the color pattern** appropriate for **filter colors**. A *filter color* `K` is one that, like `const` or `unsafe`, tracks the presence of absence of kinds of operations. Since filter colors do not change how code executes, they also do not change the result type of a `K`-block, so only some parts of the color pattern are relevant. +Next, we define a **subset of the flavor pattern** appropriate for **filter flavors**. A *filter flavor* `K` is one that, like `const` or `unsafe`, tracks the presence of absence of kinds of operations. Since filter flavors do not change how code executes, they also do not change the result type of a `K`-block, so only some parts of the flavor pattern are relevant. -Finally, we close with a section that explains the `async` color, making use of the various parts of the pattern described in the first section. +Finally, we close with a section that explains the `async` flavor, making use of the various parts of the pattern described in the first section. -## Defining the "color pattern" +## Defining the "flavor pattern" -### When is a keyword `K` a color? +### When is a keyword `K` a flavor? -We begin by defining the criteria where we, as language designers, should consider using the color pattern for a keyword `K`. Given the color patterns informality, there is no hard and fast rule for this, but the primary criteria to consider are as follows: +We begin by defining the criteria where we, as language designers, should consider using the flavor pattern for a keyword `K`. Given the flavor patterns informality, there is no hard and fast rule for this, but the primary criteria to consider are as follows: -1. The keyword `K` is used to "color" code, in the form of functions and blocks. -2. `K`-colored code works best when used with other `K`-colored code; working with "uncolored" code is limited. +1. The keyword `K` is used to "flavor" code, in the form of functions and blocks. +2. `K`-flavored code works best when used with other `K`-flavored code; working with "unflavored" code is limited. -Apart from these minimal criteria, colors can encompass a broad range of keywords with diverse effects. In Rust today, there are three keywords that meet this definition: `async`, `const`, and `unsafe`. This section focuses on `async`; the next section covers `const` and `unsafe`. +Apart from these minimal criteria, flavors can encompass a broad range of keywords with diverse effects. In Rust today, there are three keywords that meet this definition: `async`, `const`, and `unsafe`. This section focuses on `async`; the next section covers `const` and `unsafe`. -### `K`-colored functions, blocks, traits, and closures +### `K`-flavored functions, blocks, traits, and closures -The syntax to use when applying colors to functions, blocks, traits, and closures is as follows: +The syntax to use when applying flavors to functions, blocks, traits, and closures is as follows: * `K fn $name($args) -> $ty { $expr }` for functions * `K { $statements; $expr }` (and in some cases `K move { $statements; $expr }`) for blocks @@ -139,7 +135,7 @@ Function and block syntax lines up with existing, stable `async` syntax: Trait and closure syntax follows the precedent described in [RFC #3668][]: -* `async Fn` as the async version of the `Fn` trait +* `async Fn` as the async flavor of the `Fn` trait * `async || /* ... */` as the syntax for an async closure. Across all examples we see a consistent pattern that `async` appears as a prefix, consistent with its role as a kind of adjective (which in English grammar appears first). @@ -147,18 +143,18 @@ Across all examples we see a consistent pattern that `async` appears as a prefix #### What is new in this RFC 1. Resolving the unresolved question from [RFC #3668][] in the affirmative (yes, we will use the `async Fn` syntax). -2. Committing to use `async $Trait` as the syntax for any future `async`-colored traits we may add. -3. Committing to use `K $Trait` as the syntax for any other form of `K`-colored traits we may add (e.g., `const`-colored). +2. Committing to use `async $Trait` as the syntax for any future `async`-flavored traits we may add. +3. Committing to use `K $Trait` as the syntax for any other form of `K`-flavored traits we may add (e.g., `const`-flavored). #### What this RFC does NOT specify -This RFC should NOT be understood as adding any form of `K`-colored traits. For now, the only example of a `K`-colored trait are the `async Fn*` traits introduced by [RFC #3668][]. This RFC does not permit users to name an `async`-colored version of any other trait. It does however specify that *if* we support `async`-colored versions of other trait, they should use the syntax `async $TraitName`. For example, if and when we add support for an async version of the `Drop` trait, it should be named `async Drop`. Furthermore, if and when an RFC emerges from [the ongoing experimentation with const traits](#66792), that RFC should use the syntax `const $Trait`. +This RFC should NOT be understood as adding any form of `K`-flavored traits. For now, the only example of a `K`-flavored trait are the `async Fn*` traits introduced by [RFC #3668][]. This RFC does not permit users to name an `async` flavor of any other trait. It does however specify that *if* we support `async` flavors for other trait, they should use the syntax `async $TraitName`. For example, if and when we add support for an async-flavored `Drop` trait, it should be named `async Drop`. Furthermore, if and when an RFC emerges from [the ongoing experimentation with const traits](#66792), that RFC should use the syntax `const $Trait`. -### `K`-colored types +### `K`-flavored types -#### Background: what is a `K`-colored type and why might we want it? +#### Background: what is a `K`-flavored type and why might we want it? -When you tag a block or function with `async`, it changes its result type to `impl Future`, where `$ty` is the type that would normally have been produced. This characteristic is shared with some other prospective colors (e.g., [`try` and `gen`](#future-possibilities)), but not all: it corresponds to cases where the code executes differently in some way from ordinary code. +When you tag a block or function with `async`, it changes its result type to `impl Future`, where `$ty` is the type that would normally have been produced. This characteristic is shared with some other prospective flavors (e.g., [`try` and `gen`](#future-possibilities)), but not all: it corresponds to cases where the code executes differently in some way from ordinary code. Currently there is no special syntax for this type translation. That is, the only way to reference the "type produced by an async block" is to write an explicit `impl Future`. One example where this comes up frequently is when users wish to write an `async fn` that will execute *some* code eagerly and defer the rest to execute when the future is awaited. The way to do this today is to write a regular function that returns `impl Future`: @@ -182,7 +178,7 @@ To address these issues, [RFC #3628][] proposes adding a new syntax for "the typ #### Conclusion: we do want a syntax, but we don't know what it should be yet -In discussion the lang team concluded that having an explicit syntax for "the result of an async block" that was tied to the `async` keyword was a good idea. However, similarly to the question about trait syntax, the syntax is most desirable if it is part of a larger pattern. In this case, it is part of two larger patterns: one is using the `async` keyword as a prefix to transform all things related to async (functions, blocks, traits, closures, and now types). The second is that other future color keywords may employ a similar syntax (e.g., [`try`](#try-as-a-color) or [`gen`](#gen-as-a-color)). +In discussion the lang team concluded that having an explicit syntax for "the result of an async block" that was tied to the `async` keyword was a good idea. However, similarly to the question about trait syntax, the syntax is most desirable if it is part of a larger pattern. In this case, it is part of two larger patterns: one is using the `async` keyword as a prefix to transform all things related to async (functions, blocks, traits, closures, and now types). The second is that other future flavor keywords may employ a similar syntax (e.g., [`try`](#try-as-a-flavor) or [`gen`](#gen-as-a-flavor)). #### What is new in this RFC @@ -191,7 +187,7 @@ This RFC commits us to adding *some* form of this syntax for `async` but defers [apit]: https://doc.rust-lang.org/stable/reference/types/impl-trait.html#anonymous-type-parameters [rpit]: https://doc.rust-lang.org/stable/reference/types/impl-trait.html#abstract-return-types -This RFC also recommends that future colors which "transform" the result of a block or function introduce a corresponding syntax. +This RFC also recommends that future flavors which "transform" the result of a block or function introduce a corresponding syntax. Where `🚲K<$ty>` exists it should meet the following invariants: @@ -209,7 +205,7 @@ Where `🚲K<$ty>` exists it should meet the following invariants: * The definition of `🚲async<$ty>` is `impl Future`. * The "do" operation for `async` is `await`. -### Relationship between `K`-colored functions, blocks, and types +### Relationship between `K`-flavored functions, blocks, and types Looking at async functions and the proposed `🚲async<$ty>` notation, we observe the following relationship. An `async` function like `count_input`... @@ -233,13 +229,13 @@ This works because async blocks rewrite their content to produce a different val #### What is new in this RFC -This RFC recommends that future colors that define `🚲K<$ty>` meet this invariant: +This RFC recommends that future flavors that define `🚲K<$ty>` meet this invariant: -* A `K fn foo() -> T` should be equivalent to a (uncolored) `fn` that returns `🚲async` and uses some form of `K`-colored block. +* A `K fn foo() -> T` should be equivalent to a (unflavored) `fn` that returns `🚲async` and uses some form of `K`-flavored block. -## The "filter color" subset +## The "filter flavor" subset -The previous section defined the color pattern in full with reference to `async`. The `async` color has the effect of rewriting the blocks it is applied to so that the produce a different kind of value. For this reason, we call it a **rewrite color**. The other extant colors in Rust (`unsafe`, `const`) do not have this property. Instead, they are used to track the presence or absence of certain kind of operations. We call these **filter colors**; as we will explain, the full color pattern doesn't make sense for filter colors, so we define the subset that does still apply. This section focuses on `const` specifically; `unsafe` is discussed in the [Future Possibilities](#future-possibilities) section. +The previous section defined the flavor pattern in full with reference to `async`. The `async` flavor has the effect of rewriting the blocks it is applied to so that the produce a different kind of value. For this reason, we call it a **rewrite flavor**. The other extant flavors in Rust (`unsafe`, `const`) do not have this property. Instead, they are used to track the presence or absence of certain kind of operations. We call these **filter flavors**; as we will explain, the full flavor pattern doesn't make sense for filter flavors, so we define the subset that does still apply. This section focuses on `const` specifically; `unsafe` is discussed in the [Future Possibilities](#future-possibilities) section. ### At first glance, `const` and `async` seem very different @@ -249,50 +245,50 @@ In contrast, `const` limits the operations a function can perform to those that The relationship between `async` functions and `async` blocks is also different in kind than the relationship between `const` functions and `const` blocks. An `async` function is sugar for a function with an `async` block in its body (and a different return type). In contrast, a `const` function is something that can be run at compilation or runtime, and a `const` block is code that *only* runs at compilation time. -And yet, despite all these differences, both `const` and `async` intuitively *feel* very similar -- they both *feel* like colors to users, as argued in the motivation. How to understand this? +And yet, despite all these differences, both `const` and `async` intuitively *feel* very similar -- they both *feel* like flavors to users, as argued in the motivation. How to understand this? -### `const` is best understood as an inverted default for a `runtime` color +### `const` is best understood as an inverted default for a `runtime` flavor -The natural orientation for colors is additive: having a `K`-colored block gives access to additional capabilities (like awaiting values). `const`, in contrast, is a *subtractive* color. A `const` function can do fewer things than an ordinary function. To see the difference, imagine an alternate form of Rust, Runtime-Rust. +The natural orientation for flavors is additive: having a `K`-flavored block gives access to additional capabilities (like awaiting values). `const`, in contrast, is a *subtractive* flavor. A `const` function can do fewer things than an ordinary function. To see the difference, imagine an alternate form of Rust, Runtime-Rust. -"Runtime-Rust" works exactly like Rust, but the defaults are flipped: code blocks, by default, execute at compilation time. To execute at runtime, they must be tagged with `runtime { ... }`, which in turn gives them access to capabilities that are only permitted at runtime, like FFI. This `runtime` color matches `async` much more closely: just as `async` blocks can only fully be used from inside another `async` block, `runtime` blocks can only be used from inside `runtime` blocks. What this thought experiment shows is that `const` is actually an inverted default, like `?Sized`. +"Runtime-Rust" works exactly like Rust, but the defaults are flipped: code blocks, by default, execute at compilation time. To execute at runtime, they must be tagged with `runtime { ... }`, which in turn gives them access to capabilities that are only permitted at runtime, like FFI. This `runtime` flavor matches `async` much more closely: just as `async` blocks can only fully be used from inside another `async` block, `runtime` blocks can only be used from inside `runtime` blocks. What this thought experiment shows is that `const` is actually an inverted default, like `?Sized`. ### What parts of the pattern work for `runtime`? -Continuing with the Runtime-Rust hypothetical, most parts of the color pattern apply equally well to `runtime` and `async`: +Continuing with the Runtime-Rust hypothetical, most parts of the flavor pattern apply equally well to `runtime` and `async`: -* `K`-colored functions, blocks, and closures are prefixed with the keyword `K`. -* `K`-colored traits are prefixed with the keyword `K`. - * A `K`-colored trait `Trait` include (at least) the members of `Trait`, with some of them colored by `K`. -* `K`-colored functions and closures implement `K`-colored variants of the `Fn` trait. +* `K`-flavored functions, blocks, and closures are prefixed with the keyword `K`. +* `K`-flavored traits are prefixed with the keyword `K`. + * A `K`-flavored trait `Trait` include (at least) the members of `Trait`, with some of them flavored by `K`. +* `K`-flavored functions and closures implement `K`-flavored variants of the `Fn` trait. There is one part however that doesn't really fit: -* `K`-colored types can be written with the syntax `🚲K`: - * `K`-colored functions, closures, and blocks return/produce `🚲K` values, where `T` is their original type. +* `K`-flavored types can be written with the syntax `🚲K`: + * `K`-flavored functions, closures, and blocks return/produce `🚲K` values, where `T` is their original type. -Introducing a `🚲runtime` syntax to describe a "runtime-colored type" doesn't make sense, because `runtime` blocks don't change the type of value that is produced. So `🚲runtime = T` always. We don't need syntax for this and having it would be confusing. Furthermore, the equivalence between a `K`-function and a function whose body returns a `K`-block doesn't hold: +Introducing a `🚲runtime` syntax to describe a "runtime-flavored type" doesn't make sense, because `runtime` blocks don't change the type of value that is produced. So `🚲runtime = T` always. We don't need syntax for this and having it would be confusing. Furthermore, the equivalence between a `K`-function and a function whose body returns a `K`-block doesn't hold: * `runtime fn $name() -> T { $expr }` is NOT equivalent to `fn $name() -> T { runtime { $expr } }` -This equivalence doesn't work because the `runtime` color cannot be *encapsulated*. You can't have a compilation time function that uses a `runtime` block internally but hides it from its callers. In contrast, you *can* have a synchronous Rust function that creates an `async` block internally and executes it by polling in place or some other means. +This equivalence doesn't work because the `runtime` flavor cannot be *encapsulated*. You can't have a compilation time function that uses a `runtime` block internally but hides it from its callers. In contrast, you *can* have a synchronous Rust function that creates an `async` block internally and executes it by polling in place or some other means. -### The filter color subset of the color pattern +### The filter flavor subset of the flavor pattern -Based on our discussion of `runtime`, we can identify the parts of the full color pattern that apply to filter colors: +Based on our discussion of `runtime`, we can identify the parts of the full flavor pattern that apply to filter flavors: * `K`-functions using the syntax `K fn $name() -> $ty`; * `K`-blocks using the syntax `K { $expr }` (and potentially `K move { $expr }`); * `K`-traits using the syntax `K $Trait`; - * `K`-colored traits should offer at least the same methods, associated types, and other trait items as an uncolored version of the trait. Some of the items will be `K`-colored, but not necessarily all of them. + * `K`-flavored traits should offer at least the same methods, associated types, and other trait items as the unflavored trait. Some of the items will be `K`-flavored, but not necessarily all of them. * `K`-closures using the syntax `K [move] |$args| $expr` to define a K-closure; * Closures implement the `K Fn` traits. -The other parts of the pattern (e.g., the `🚲K` syntax or defining a "do" operation) do not apply because filter colors don't change the type of the value produced by the block. Further, the transformation from `K fn $name` to a function with a `K`-block doesn't work, because the point of a `K fn $name()` is to signal to callers that `$name` can only be called inside of a `K`-block. +The other parts of the pattern (e.g., the `🚲K` syntax or defining a "do" operation) do not apply because filter flavors don't change the type of the value produced by the block. Further, the transformation from `K fn $name` to a function with a `K`-block doesn't work, because the point of a `K fn $name()` is to signal to callers that `$name` can only be called inside of a `K`-block. -## Teaching async via the "color pattern" +## Teaching async via the "flavor pattern" -> The "color pattern" is meant to guide Rust designers as we extend the language, not to be something directly taught to end users. To illustrate how it might feel, this section covers an example of teaching `async` leveraging the syntax and concepts of the color pattern (but not teaching the pattern explicitly). To help illustrate where the overall vision for Rust, we assume that `🚲K<$T>` is `K -> $T` (a variant of [RFC #3628][] currently preferred by the author), that `async -> T` and its equivalent `impl Trait` are supported in local variable declarations ([RFC #2071][]), and that there is some form of `async Iterator` trait available in std. +> The "flavor pattern" is meant to guide Rust designers as we extend the language, not to be something directly taught to end users. To illustrate how it might feel, this section covers an example of teaching `async` leveraging the syntax and concepts of the flavor pattern (but not teaching the pattern explicitly). To help illustrate where the overall vision for Rust, we assume that `🚲K<$T>` is `K -> $T` (a variant of [RFC #3628][] currently preferred by the author), that `async -> T` and its equivalent `impl Trait` are supported in local variable declarations ([RFC #2071][]), and that there is some form of `async Iterator` trait available in std. ### Async functions @@ -482,93 +478,90 @@ We have thus far avoided saying precisely what a future *is*, apart from a "susp This section collects the precise statements of this RFC. -## When is a keyword `K` a color? +## When is a keyword `K` a flavor? There is no hard and fast rule for this, but the primary criteria to consider are as follows: 1. The keyword `K` should be applicable to functions and blocks of code. * `const fn foo()`, `const { .. }` * `async fn foo()`, `async { .. }`, `async move { .. }` -2. `K`-colored code should generally only be usable with other `K`-colored code. +2. `K`-flavored code should generally only be usable with other `K`-flavored code. * `const` functions can only call other `const` functions * `async` functions return futures that can only be awaited from async blocks -## Categories of colors +## Categories of flavors -Colors fall into two, non-exlusive, categories: +Flavors fall into two, non-exlusive, categories: -* *Rewrite* colors changes how a `K`-colored block executes, resulting in a distinct type from the block vs an uncolored block. -* *Filter* colors either expand or restrict the set operations that can be performed in a `K`-colored block. +* *Rewrite* flavors changes how a `K`-flavored block executes, resulting in a distinct type from the block vs an unflavored block. +* *Filter* flavors either expand or restrict the set operations that can be performed in a `K`-flavored block. -## The complete color pattern +## The complete flavor pattern -Every color keyword `K` supports: +Every flavor keyword `K` supports: * `K`-functions using the syntax `K fn $name() -> $ty`; * `K`-blocks using the syntax `K { $expr }` (and potentially `K move { $expr }`); * `K`-traits using the syntax `K $Trait`; - * `K`-colored traits should offer at least the same methods, associated types, and other trait items as an uncolored version of the trait. Some of the items will be `K`-colored, but not necessarily all of them. + * `K`-flavored traits should offer at least the same methods, associated types, and other trait items as the unflavored trait. Some of the items will be `K`-flavored, but not necessarily all of them. * `K`-closures using the syntax `K [move] |$args| $expr` to define a K-closure; * Closures implement the `K Fn` traits. -Rewrite colors further support: +Rewrite flavors further support: * `K`-types using a TBD syntax, denoted `🚲K<$ty>` for now. * A `K`-type `🚲K<$ty>` is the type resulting from a `K`-block whose expression has type `$ty`. -* A `K`-function `K fn $name($args) -> $ty { $expr }` is "roughly" equivalent to a regular function whose return type and body are `K`-colored: +* A `K`-function `K fn $name($args) -> $ty { $expr }` is "roughly" equivalent to a regular function whose return type and body are `K`-flavored: * `fn $name($args) -> 🚲K<$ty> { K { $expr } }` - * The translation will vary slightly depending on the specifics of the color. The goal is that `K`-functions should behave as close as possible to an uncolored function. For example, drop order should be preserved. -* A `K`-closure returns `🚲K<$ty>`, where `$ty` would have been the return type had the closure been uncolored. + * The translation will vary slightly depending on the specifics of the flavor. The goal is that `K`-functions should behave as close as possible to an unflavored function. For example, drop order should be preserved. +* A `K`-closure returns `🚲K<$ty>`, where `$ty` would have been the return type had the closure been unflavored. * A "do" operation that, when executed in a `K`-block, consumes a `🚲K<$ty>` and produces a `$ty` value. -Some colors may only include a subset of these features. This subset should be documented and explained. +Some flavors may only include a subset of these features. This subset should be documented and explained. # Rationale, alternatives, and FAQ [rationale-and-alternatives]: #rationale-and-alternatives -## So...what does it ACTUALLY MEAN to say that a keyword `K` "is a color"? +## So...what does it ACTUALLY MEAN to say that a keyword `K` "is a flavor"? Answer: nothing. It's just an observation of fact! `K` is a keyword that can be applied to blocks and functions and which affects how code can interoperate. -Longer answer: it suggests that `K` should support the syntaxes and operations described in this RFC. Each color is different, so it is possible that a given piece of syntax is not relevant, but you ought to be able to explain why. Certainly past experience with colors suggests that over time you will want to be able to use them in all the places identified in the color pattern to allow people to write code naturally.g +Longer answer: it suggests that `K` should support the syntaxes and operations described in this RFC. Each flavor is different, so it is possible that a given piece of syntax is not relevant, but you ought to be able to explain why. Certainly past experience with flavors suggests that over time you will want to be able to use them in all the places identified in the flavor pattern to allow people to write code naturally.g -## What about `mut`, is that a color? +## What about `mut`, is that a flavor? -The `mut` keyword does not qualify as a color per the definition in this RFC because it cannot be applied to functions or blocks. As such, it's not clear how one would extend `mut` to apply to other locations like traits or closures. +The `mut` keyword does not qualify as a flavor per the definition in this RFC because it cannot be applied to functions or blocks. As such, it's not clear how one would extend `mut` to apply to other locations like traits or closures. -However, it *is* true that `mut` is part of what often feels like "3 modes" in Rust: `self`, `&self`, and `&mut self`. It is possible to imagine creating some sort of color such that e.g. the `Fn`, `FnMut`, and `FnOnce` traits, for example, would be colored variants of a single "callable" trait. +However, it *is* true that `mut` is part of what often feels like "3 modes" in Rust: `self`, `&self`, and `&mut self`. It is possible to imagine creating some sort of flavor such that e.g. the `Fn`, `FnMut`, and `FnOnce` traits, for example, would be flavored variants of a single "callable" trait. Another similar split is `Send` vs not `Send`. We don't have a keyword for this, but especially in async code it is a very real split. -## Why did you use the word "color" and not "effect"? - -clarfonthey [asked](https://github.com/rust-lang/rfcs/pull/3710#issuecomment-2405647443): - -> So, I understand why you're referring to these as colours, since "coloured functions" is what the larger programming language community uses as a term, but I think that the naming of effects should be used instead, especially since that's at least what the current Rust WGs have been settling on. - -I avoided the term "effect" for a few reasons. +## Why did you use the word "flavor"? -One of them is that I think that it is overall kind of jargon. What's more, my observation is that it is *divisive* jargon, as people bring pre-conceived notions of what it *ought* to mean, and not everything that I consider a "color" fits into those notions. +During the RFC period we evaluated several possible names. The term "flavor" was ultimately chosen because it did very well along three criteria: -My take on an *effect* is that it is some kind of "operation" that occurs during execution, such as a write to a specific memory region, a panic, a memory allocation, etc. It's reasonable to model this kind of effect as a "function" you can call when that event occurs (perhaps with some arguments). +* **Memorable and specific:** A term that is too common and generic can be confusing. This can be because the word is used too much for too many things or because the word is just such a common English word that it doesn't *sound* like a specific thing. +* **Grammatically flexible:** It's good to have a term that can be used in multiple ways, for example as an adjective, noun, etc. +* **Approachable:** When I say "jargon" what I often mean is that it's a word that kind of has a formidable feeling, like "monad". When you hear the term you instantly feel a little bit excluded and less smart (or, alternatively, when you know the term well and use it correctly, you feel a bit smarter). That seems like a problem to me. +* **Familiar:** it's good to use a term that people find familiar and that gives them the right intuitions, even if it's not aligned on every detail. -From what I can tell, this definition lines up with Koka (which is a very cool language). However, Koka is also (I believe) based on Continuation Passing Style, which means that simple function calls get a lot more power. This allows them to model e.g. generators or exceptions as effects. +### Other options considered -To my mind, this is kind of cheating, or at least *misleading*. In particular, we can't "just" port over Koka's abstractions to Rust because we also have to account for rewrites. +**Effect** is reasonably memorable and specific but it not familiar nor approachable to people in conversation. It is also not grammatically flexible. For example, the RFC frequently talks about *`K`-flavored traits*, but it's not clear what the adjective form of an effect is (`K`-effected traits?). -In any case, I'm open to a terminology discussion, though *personally* I'd be inclined not to rename colors to effects, but perhaps to rename *filter colors* to *effect* colors or *effect-carrying* colors. +**Color** was initially chosen as a playful allusion to the ["What color is your function"?][WCIF] blog post. Color is approachable but such a common word in conversation that readers may not realize it is a "term of art". It is also widely used in some domains, such as GUIs, making it a poor choice for keyword (not that we are proposing a keyword). Also, while color is relatively grammatically flexible, it can still be awkward. For example, we sometimes talk about a specific "flavor" of a trait, and saying "the async color of the Trait" doesn't sound right. There was also the problem that, as y'all are hopefully aware, the term "colored" has been associated with some awful parts of human history, and some common phrasings could have undesirable connotations. ## Why include `🚲K<$T>` syntax? -Most parts of the color pattern already exist in Rust today or at least in accepted RFCs. The `🚲K<$T>` syntax for colored types stands out as the exception. It was included in the RFC because it forms an important part of the overall story (witness how prominent it is in the guide section). Some members of the lang team felt that, without `🚲K<$T>`, they didn't feel good about the color pattern overall. +Most parts of the flavor pattern already exist in Rust today or at least in accepted RFCs. The `🚲K<$T>` syntax for flavored types stands out as the exception. It was included in the RFC because it forms an important part of the overall story (witness how prominent it is in the guide section). Some members of the lang team felt that, without `🚲K<$T>`, they didn't feel good about the flavor pattern overall. -## Why not use the camel-case named for K-colored traits, like `AsyncFn` instead of `async Fn`? +## Why not use the camel-case named for K-flavored traits, like `AsyncFn` instead of `async Fn`? -We considered a number of alternatives to `K Trait` for denoting K-colored traits. Perhaps the most obvious was a camelcased identifier like `AsyncFn`. While this is a perfectly viable option, it has downsides as well: +We considered a number of alternatives to `K Trait` for denoting K-flavored traits. Perhaps the most obvious was a camelcased identifier like `AsyncFn`. While this is a perfectly viable option, it has downsides as well: * First, the story of how to transition something from sync to async gets more complicated. It's not a story of "just add `async` keywords in the right places". * Second, this convention does not offer an obvious way to support const traits like `const Default`, unless we are going to produce `ConstDefault` variants as well somehow. And if there are more variants in the future, e.g., `AsyncSendSomeTrait` or `AsyncTrySendSomeTrait`, it becomes very unwieldy. -* Third, although we are not committing to any form of "color generics" in this RFC, we would also prefer not to close the door entirely; using an entirely distinct identifier like `AsyncFn` would make it very difficult to imagine how one function definition could be made generic over a color. +* Third, although we are not committing to any form of "flavor generics" in this RFC, we would also prefer not to close the door entirely; using an entirely distinct identifier like `AsyncFn` would make it very difficult to imagine how one function definition could be made generic over a flavor. Ultimately, the killer argument was that this felt like everybody's preferred "second choice option", but nobody's *favorite*. It's not a bold design. @@ -579,13 +572,13 @@ One advantage of today's sugar for trait bounds (`T: Fn()`, `T: FnMut()`, etc) i * There is no obvious way to encode `T: FnMut()` or `FnOnce()`. Should the notation be `T: fn mut()`and `T: fn once()`? Then we are losing the parallel to function declarations and introducing some ad-hoc keywords. * Changing the notation for `T: Fn()` bounds is a massive change affects virtually all existing Rust code. To make a change like that, we have to be very confident the change will be a win, and many were not (particularly given the previous bullet). -## How did you come up with "rewrite" color and "filter" color? +## How did you come up with "rewrite" flavor and "filter" flavor? -We deliberately chose non-standard terms to avoid associations. For example, the "filter" color might also be called an "effect-carrying color", but that invites debate as to the precise definition of an effect and whether `unsafe` qualifies. +We deliberately chose non-standard terms to avoid associations. For example, the "filter" flavor might also be called an "effect-carrying flavor", but that invites debate as to the precise definition of an effect and whether `unsafe` qualifies. ## Can `🚲K<$T>` be used in closure return types like it can for regular functions? -The answer to this is complicated and left to be resolved by future RFCs that actually add the `🚲K<$T>` notation. Recall that we specified that `K`-colors can be migrated from a `K`-function to its return type and body: +The answer to this is complicated and left to be resolved by future RFCs that actually add the `🚲K<$T>` notation. Recall that we specified that `K`-flavors can be migrated from a `K`-function to its return type and body: ```rust K fn foo() -> T { ... } @@ -601,21 +594,21 @@ K |args| -> T { ... } |args| -> 🚲K { K { ... } } ``` -Supporting this notation is tricky however because closure return types are often elided. Given that `K`-closure types implement `K Fn` rather than the typical `Fn` trait, we need to be able to determine if a closure has color `K` to decide what traits it implements. But there is no way to distinguish whether an expression like `|| K { .. }` is meant to be a `K`-closure that implements `K Fn` or an uncolored closure that implements `Fn>` value. As described in [RFC #3668][], these two traits are not equivalent for `async` and likely not for other future rewrite colors. +Supporting this notation is tricky however because closure return types are often elided. Given that `K`-closure types implement `K Fn` rather than the typical `Fn` trait, we need to be able to determine if a closure has flavor `K` to decide what traits it implements. But there is no way to distinguish whether an expression like `|| K { .. }` is meant to be a `K`-closure that implements `K Fn` or an unflavored closure that implements `Fn>` value. As described in [RFC #3668][], these two traits are not equivalent for `async` and likely not for other future rewrite flavors. We've left the behavior here to be specified in a future RFC, but we note that it would be simple to declare it as an error for now. Note that `-> impl Future` is also not accepted in this position. # Prior art [prior-art]: #prior-art -The term "color" is taken from the ["What color is your function?"][WCIF] blog post. As that post indicates, many languages have added colors of one form or another. This RFC is focused on the syntactic patterns around colors, specifically using the color as a prefix (e.g., `async fn`, `🚲async`, `async Trait`). This pattern of prefixing functions with colors is very common and is very natural in English as the color plays the role of an adjective, describing the kind of function one has. +The term "flavor" is taken from the ["What flavor is your function?"][WCIF] blog post. As that post indicates, many languages have added flavors of one form or another. This RFC is focused on the syntactic patterns around flavors, specifically using the flavor as a prefix (e.g., `async fn`, `🚲async`, `async Trait`). This pattern of prefixing functions with flavors is very common and is very natural in English as the flavor plays the role of an adjective, describing the kind of function one has. -Although the RFC is focused on syntax, it does also suggest some equivalences that colors must maintain, such as how a `K fn foo() -> T { expr }` can be transformed to `fn foo() -> 🚲K { K move { expr } }` for any rewrite color `K`. These equivalences hint at an underlying semantics for colors. There are two related precedents in academia: +Although the RFC is focused on syntax, it does also suggest some equivalences that flavors must maintain, such as how a `K fn foo() -> T { expr }` can be transformed to `fn foo() -> 🚲K { K move { expr } }` for any rewrite flavor `K`. These equivalences hint at an underlying semantics for flavors. There are two related precedents in academia: -* [Haskell]'s monads are comparable in some ways to rewrite colors, and the [monadic laws][] suggest similar equivalences. A "[monad]" is like a color made up of two operations, one that "wraps" a value `v` to yield a `Wrapped` (analogous to `K-type!(v)`) and one that does an `and_then` operation, often called "fold", taking a `Wrapped`and a `V => Wrapped` closure and yielding a `Wrapped` value. The effect is kind of like a "programmable semicolon", allowing [Haskell] programs to introduce operations in in between each statement, and to potentially carry along "user data" with the wrapped `V` value. Haskell includes generic syntax ("do"-notation) designed to work with monads, allowing one to define generic functions that can be used with any monad `Wrapped`. - * Monads are famously challenging to learn ("The sheer number of different monad tutorials on the internet is a good indication of the difficulty many people have understanding the concept", says the Haskell wiki). Colors run the risk of being similarly complicated. However, this RFC is not introducing colors as a concept that users would be taught, but rather as a syntactic pattern that they will pick up on as they learn about different existing parts of Rust (async, unsafe, etc). - * Ergonomic challenges with monads often arise when composing or converting between monads. These same problems arise with colors (e.g., how to call an async function from a sync function), but those problems already exist and are not made worse by this RFC.) -* Effects in languages like [Koka][] can be compared to colors. Effects have evolved over the years from a way of signalling side effects (from which the name derived) to an expressive construct for injecting new operations into functions. An effect in [Koka][] is a function that is threaded down through context, the usage of which must be declared. Because [Koka][] is based on [continuation passing style][cps], effect functions are able to abort computation (modeling exceptions) or pause and resume it (modeling generators). +* [Haskell]'s monads are comparable in some ways to rewrite flavors, and the [monadic laws][] suggest similar equivalences. A "[monad]" is like a flavor made up of two operations, one that "wraps" a value `v` to yield a `Wrapped` (analogous to `K-type!(v)`) and one that does an `and_then` operation, often called "fold", taking a `Wrapped`and a `V => Wrapped` closure and yielding a `Wrapped` value. The effect is kind of like a "programmable semicolon", allowing [Haskell] programs to introduce operations in in between each statement, and to potentially carry along "user data" with the wrapped `V` value. Haskell includes generic syntax ("do"-notation) designed to work with monads, allowing one to define generic functions that can be used with any monad `Wrapped`. + * Monads are famously challenging to learn ("The sheer number of different monad tutorials on the internet is a good indication of the difficulty many people have understanding the concept", says the Haskell wiki). Flavors run the risk of being similarly complicated. However, this RFC is not introducing flavors as a concept that users would be taught, but rather as a syntactic pattern that they will pick up on as they learn about different existing parts of Rust (async, unsafe, etc). + * Ergonomic challenges with monads often arise when composing or converting between monads. These same problems arise with flavors (e.g., how to call an async function from a sync function), but those problems already exist and are not made worse by this RFC.) +* Effects in languages like [Koka][] can be compared to flavors. Effects have evolved over the years from a way of signalling side effects (from which the name derived) to an expressive construct for injecting new operations into functions. An effect in [Koka][] is a function that is threaded down through context, the usage of which must be declared. Because [Koka][] is based on [continuation passing style][cps], effect functions are able to abort computation (modeling exceptions) or pause and resume it (modeling generators). [Koka]: https://koka-lang.github.io/koka/doc/index.html [Haskell]: https://www.haskell.org/ @@ -634,37 +627,37 @@ This question is purposefully left unresolved by this RFC and is meant to be add # Future possibilities [future-possibilities]: #future-possibilities -## Expanding on our existing colors +## Expanding on our existing flavors -This RFC suggests a number of future changes to "fill out" the support for colors: +This RFC suggests a number of future changes to "fill out" the support for flavors: * Async should add a `🚲async` syntax (see [RFC #3628][]). -* We will eventually need a mechanism for defining async-colored traits like `async Iterator` or `async Default`. For the short term this is not urgent as the traits we are focused on (such as `Fn` and `Drop`) are special. -* Const-colored traits should use a notation like (e.g.) `const Default`. -* Unsafe-colored traits like `unsafe Default`, which would mean a `Default` trait with an unsafe `default` method, are a possibility, but they could be easily confused for an unsafe trait. +* We will eventually need a mechanism for defining async-flavored traits like `async Iterator` or `async Default`. For the short term this is not urgent as the traits we are focused on (such as `Fn` and `Drop`) are special. +* Const-flavored traits should use a notation like (e.g.) `const Default`. +* Unsafe-flavored traits like `unsafe Default`, which would mean a `Default` trait with an unsafe `default` method, are a possibility, but they could be easily confused for an unsafe trait. -## More complex colors +## More complex flavors -In authoring this RFC, we also explored what future colors might look like. Three potential future colors are `unsafe`, `try` (for fallible functions that yield a `Result`, most commonly at least) and `gen` (for generators). `try` and `gen` would be rewrite colors. +In authoring this RFC, we also explored what future flavors might look like. Three potential future flavors are `unsafe`, `try` (for fallible functions that yield a `Result`, most commonly at least) and `gen` (for generators). `try` and `gen` would be rewrite flavors. -Unlike `async` and `const`, these three keywords are not themselves *colors* but more like *families of colors*. `unsafe` for example indicates that the function has safety predicates that must be proven before it can be called and hence the "true color" conceptually includes those predicates (though we don't write them explicitly in our notation). `try` and `gen` both have other types associated with them beyond the main output type. +Unlike `async` and `const`, these three keywords are not themselves *flavors* but more like *families of flavors*. `unsafe` for example indicates that the function has safety predicates that must be proven before it can be called and hence the "true flavor" conceptually includes those predicates (though we don't write them explicitly in our notation). `try` and `gen` both have other types associated with them beyond the main output type. -### `unsafe` as a filter color +### `unsafe` as a filter flavor -`unsafe` is a filter color. The `unsafe` color allows operations that are not proven safe using the Rust type system and which therefore require user validation. In contrast to some filter colors, `unsafe` doesn't actually change the set of operations that a given piece of code can perform *at runtime* -- a safe block can invoke a safe functon which includes an unsafe block (and most do), so safe blocks can perform the same actions as unsafe blocks. Furthermore the `unsafe` color, despite being 1 keyword, is really a "set" of colors, in that each `unsafe fn` has its own safety requirements that are distinct from one another. +`unsafe` is a filter flavor. The `unsafe` flavor allows operations that are not proven safe using the Rust type system and which therefore require user validation. In contrast to some filter flavors, `unsafe` doesn't actually change the set of operations that a given piece of code can perform *at runtime* -- a safe block can invoke a safe functon which includes an unsafe block (and most do), so safe blocks can perform the same actions as unsafe blocks. Furthermore the `unsafe` flavor, despite being 1 keyword, is really a "set" of flavors, in that each `unsafe fn` has its own safety requirements that are distinct from one another. -Treating `unsafe` as a filter color would mean adding the following pieces of syntax: +Treating `unsafe` as a filter flavor would mean adding the following pieces of syntax: * an `unsafe $TraitName` syntax, where some operations in `$TraitName` unsafe; * unsafe closures `|$args| $expr` that implement `unsafe Fn`. This would close an existing "wart" in the language: a safe `fn foo()` implements the `Fn()` trait, but an `unsafe fn foo()` does not (because the "call" method in `Fn` is safe). Similarly, the type `fn()` of safe function pointers implements `Fn`, but the type `unsafe fn()` of unsafe function pointers does not. -There is a complication to considering `unsafe` as a color because we already have a notion of an `unsafe` trait, with a distinct meaning. An unsafe trait is one where implementors prove a safety predicate relied on by the caller, as opposed to an `unsafe`-colored trait, which is a trait where the caller proves a safety predicate relied upon by the impl. +There is a complication to considering `unsafe` as a flavor because we already have a notion of an `unsafe` trait, with a distinct meaning. An unsafe trait is one where implementors prove a safety predicate relied on by the caller, as opposed to an `unsafe`-flavored trait, which is a trait where the caller proves a safety predicate relied upon by the impl. -It's also worth noting the `unsafe_op_in_unsafe_fn` lint, which encourages the use of `unsafe` blocks even within an `unsafe` function. This is likely to be different from other effect-carrying colors that could be added (e.g., perhaps one that tracks whether allocation or panics can occur in a function). This difference stems in part from the fact that `unsafe` is not tracking an "effect" that occurs at runtime but rather an aid to static reasoning. +It's also worth noting the `unsafe_op_in_unsafe_fn` lint, which encourages the use of `unsafe` blocks even within an `unsafe` function. This is likely to be different from other effect-carrying flavors that could be added (e.g., perhaps one that tracks whether allocation or panics can occur in a function). This difference stems in part from the fact that `unsafe` is not tracking an "effect" that occurs at runtime but rather an aid to static reasoning. -### `try` as a rewrite color +### `try` as a rewrite flavor The `try` keyword was introduced in [RFC #243][], along with the `?` operator. It has been unstable since [RFC #243][] was merged in 2016 and has seen significant evolution. @@ -673,13 +666,13 @@ As described in [RFC #243][], a `try { $expr }` block executes `$expr` and "capt [#41414]: https://github.com/rust-lang/rust/issues/41414 [#70941]: https://github.com/rust-lang/rust/issues/70941 -Looking at `try` as a color, it is clear that ok-wrapping is the correct and consistent behavior. `try`, like `async`, is a fully general color that transforms its result type, and the color pattern specifies that the type of an expression in a block should be "ok-wrapped" to produce the block's final output type. Another point of controversy about `try` has been how to support it syntactically at the function level. This RFC suggests that the right answer is `try fn foo() -> T`, where `T` represents the "ok" type. The final result of calling `foo()` would therefore be `🚲try`, the ok-wrapped version of `T`. +Looking at `try` as a flavor, it is clear that ok-wrapping is the correct and consistent behavior. `try`, like `async`, is a fully general flavor that transforms its result type, and the flavor pattern specifies that the type of an expression in a block should be "ok-wrapped" to produce the block's final output type. Another point of controversy about `try` has been how to support it syntactically at the function level. This RFC suggests that the right answer is `try fn foo() -> T`, where `T` represents the "ok" type. The final result of calling `foo()` would therefore be `🚲try`, the ok-wrapped flavor of `T`. -While treating `try` as a color is appealing, it does have one important difference from `async`: `try` and `?` are intended to be usable with many types, including `Result`, `Option`, and others. In terms of the color pattern, it is therefore unclear what the syntax `🚲try` should expand to. +While treating `try` as a flavor is appealing, it does have one important difference from `async`: `try` and `?` are intended to be usable with many types, including `Result`, `Option`, and others. In terms of the flavor pattern, it is therefore unclear what the syntax `🚲try` should expand to. [`Try`]: https://doc.rust-lang.org/std/ops/trait.Try.html -One possibility is permitting the user to explicitly declare (and important) `try` colors themselves in the form of a type alias. For example, the `std::io::Result` pattern might instead be: +One possibility is permitting the user to explicitly declare (and important) `try` flavors themselves in the form of a type alias. For example, the `std::io::Result` pattern might instead be: ```rust // Older alias, deprecated: @@ -700,7 +693,7 @@ try fn load_configuration_string() -> String { } ``` -This would be equivalent to the following uncolored function returning `🚲try`: +This would be equivalent to the following unflavored function returning `🚲try`: ```rust use std::io::try; @@ -715,11 +708,11 @@ This is only one possibility. There are several other ideas for how try could be * Define `type try = Result` as an alias for the residual and use `try -> T` to mean `impl Try` (this assumes `🚲try` is defined as `try -> T`). * Extend `try fn` with a clause like `try fn foo() -> T throws X` and have it expand to `impl Try` or something like that. -### `gen` as a rewrite color +### `gen` as a rewrite flavor -The `gen` keyword has been proposed for introducing generator blocks, which are a syntactic pattern to make it easier to write iterators (and perhaps to fill other use cases, there is a range of design space still to be covered). One notation proposed for `gen` is `gen`, which appears at first glance to resemble the proposed `async` type notation from [RFC #3628]. However, as proposed, `gen` is actually quite different, as the `T` here represents the type of value that is yielded by the iterator. Therefore the generator may produce any number of `T` instances, whereas `async` produces exactly one `T` instance. This means that they are used very differently by users, and it is unclear whether what parts of the color pattern should apply to `gen` in this case. +The `gen` keyword has been proposed for introducing generator blocks, which are a syntactic pattern to make it easier to write iterators (and perhaps to fill other use cases, there is a range of design space still to be covered). One notation proposed for `gen` is `gen`, which appears at first glance to resemble the proposed `async` type notation from [RFC #3628]. However, as proposed, `gen` is actually quite different, as the `T` here represents the type of value that is yielded by the iterator. Therefore the generator may produce any number of `T` instances, whereas `async` produces exactly one `T` instance. This means that they are used very differently by users, and it is unclear whether what parts of the flavor pattern should apply to `gen` in this case. -That said, there is some precedent for treating "generators" as color-*like* things. In Haskell, the `List` monad performs implicit `flat_map` operations, meaning that a given piece of code may execute many times. In Rust terms, this would correspond to nested `for` loops. Consider the following Rust-like pseudocode: +That said, there is some precedent for treating "generators" as flavor-*like* things. In Haskell, the `List` monad performs implicit `flat_map` operations, meaning that a given piece of code may execute many times. In Rust terms, this would correspond to nested `for` loops. Consider the following Rust-like pseudocode: ```rust gen fn even() yields u32 { @@ -749,14 +742,14 @@ gen fn all() yields u32 { } ``` -Treating `gen` as a `flat_map` operation does not map to the "do" operation defined in the color pattern. The "do" operation is meant to take a transformed value of type `🚲gen` and produce a single value of type `T`; but `flat_map` produces any number of `T` values. This is not necessarily a problem, not all colors have to provide all parts of the pattern, but it would suggest that `gen` is a distinct category of color from a rewrite color like `async` or `try`. +Treating `gen` as a `flat_map` operation does not map to the "do" operation defined in the flavor pattern. The "do" operation is meant to take a transformed value of type `🚲gen` and produce a single value of type `T`; but `flat_map` produces any number of `T` values. This is not necessarily a problem, not all flavors have to provide all parts of the pattern, but it would suggest that `gen` is a distinct category of flavor from a rewrite flavor like `async` or `try`. There is however another "do" operation we might use for generators which *does* fit the rewrite pattern, but which may not be as intuitive or useful to end users. In addition to the yield type that we have been discussing, generators can be extended with the idea of a final value type; we can use the syntax `🚲gen`, where `Y` is the *yielding* type and `F` is the *final* type that is returned. (An existing `impl Iterator` can be seen as having a yield type of `I` and a final type of `()`.) In this case, the "do" operation would be `yield_all`, which would yield up all `Y` items and then return the final `F` value. -Here is an example using the hypothesized "yield all" construct, showing how you could work with `gen` as a color. In this case, the `yield_all` 'unwraps' the generator effect, propagating the yielded values, and returning the final value. Calling `outer` would then yield up [`0`, `1`, `2`, `6`, `7`, `8`] and return 42. Note how `.yield_all` appears at the same places that `.await` would appear if these were async functions: +Here is an example using the hypothesized "yield all" construct, showing how you could work with `gen` as a flavor. In this case, the `yield_all` 'unwraps' the generator effect, propagating the yielded values, and returning the final value. Calling `outer` would then yield up [`0`, `1`, `2`, `6`, `7`, `8`] and return 42. Note how `.yield_all` appears at the same places that `.await` would appear if these were async functions: ```rust -// Example of `gen` as a color. +// Example of `gen` as a flavor. // // Clearly this syntax is suboptimal, it is only // meant to illustrate the concept. @@ -792,4 +785,4 @@ gen fn outer() -> u32 yielding u32 {} gen fn outer() -> u32, Yielding = u32 {} ``` -Clearly more exploration is needed here. The best option may be to say that `gen` does not follow the rewrite color pattern in its full particulars. +Clearly more exploration is needed here. The best option may be to say that `gen` does not follow the rewrite flavor pattern in its full particulars. From 8d83e10ea4cc87a1cf42ce77aa0959c4147f3536 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 15 Oct 2024 14:52:14 -0400 Subject: [PATCH 06/11] rename "color" to "flavor" --- ...-design-pattern.md => 0000-flavor-syntactic-design-pattern.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{0000-color-syntactic-design-pattern.md => 0000-flavor-syntactic-design-pattern.md} (100%) diff --git a/text/0000-color-syntactic-design-pattern.md b/text/0000-flavor-syntactic-design-pattern.md similarity index 100% rename from text/0000-color-syntactic-design-pattern.md rename to text/0000-flavor-syntactic-design-pattern.md From 7f469bcfd0f1e22dfd192b38c5053c2c50887135 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 15 Oct 2024 14:54:04 -0400 Subject: [PATCH 07/11] adopt the "house" emoji instead of "bike" It is meant to symbolize a "shed". --- text/0000-flavor-syntactic-design-pattern.md | 92 ++++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/text/0000-flavor-syntactic-design-pattern.md b/text/0000-flavor-syntactic-design-pattern.md index 815bcd7a88f..12afd8128de 100644 --- a/text/0000-flavor-syntactic-design-pattern.md +++ b/text/0000-flavor-syntactic-design-pattern.md @@ -28,20 +28,20 @@ In the flavor pattern, each flavor is tied to a specific keyword `K`. Flavors sh Some flavors rewrite code so that it executes differently (e.g., `async`). These are called **rewrite flavors**. Each such flavor should have the following: -* A syntax `🚲K<$ty>` defining the `K`-type, the type that results from a `K`-block, `K`-function, or `K`-closure whose body has type `$ty`. - * The `🚲K<$ty>` is a placeholder. We expect a future RFC to define the actual syntax. -* A "do" operation that, when executed in a `K`-block, consumes a `🚲K<$ty>` and produces a `$ty` value. +* A syntax `🏠K<$ty>` defining the `K`-type, the type that results from a `K`-block, `K`-function, or `K`-closure whose body has type `$ty`. + * The `🏠K<$ty>` is a placeholder. We expect a future RFC to define the actual syntax. +* A "do" operation that, when executed in a `K`-block, consumes a `🏠K<$ty>` and produces a `$ty` value. * The property that a `K`-function can be transformed to a regular function with a `K`-flavored return type and body. * i.e., the following are roughly equivalent (the precise translation can vary so as to e.g. preserve drop order): * `K fn $name($args) -> $ty { $expr }` - * `fn $name($args) -> 🚲K<$ty> { K { $expr } }` + * `fn $name($args) -> 🏠K<$ty> { K { $expr } }` ## Binding recommendations Existing flavor-like keywords in the language do not have all of these parts. The RFC therefore includes a limited set of binding recommendations that brings them closer to conformance: * Commit to `K $Trait` as the syntax for applying flavors to traits, with the `async Fn`, `async FnMut`, and `async FnOnce` traits being the only current usable example. -* Commit to adding a TBD syntax `🚲async<$ty>` that will meet the equivalences described in this RFC. +* Commit to adding a TBD syntax `🏠async<$ty>` that will meet the equivalences described in this RFC. ## Not part of this RFC @@ -53,7 +53,7 @@ The [Future Possibilities](#future-possibilities) discusses other changes we cou * For example, the RFC specifies that **if** we add an async flavor of the `Read` trait, it will be referred to as `async Read`, but the RFC does **not** specify whether to add such a trait nor how such a trait would be defined or what its contents would be. * How `const Trait` ought to work ([under active exploration][#67792]): * The RFC only specifies that the syntax for naming a `const`-flavored trait should be `const Trait`; it does not specify what a `const`-flavored trait would mean or when that syntax can be used. -* What specific syntax we should use for `🚲K<$ty>`: +* What specific syntax we should use for `🏠K<$ty>`: * We are committed to adding this syntax at least for `async`, but the precise syntax still needs to be pinned down. [RFC #3628][] contains one possibility. # Motivation @@ -182,32 +182,32 @@ In discussion the lang team concluded that having an explicit syntax for "the re #### What is new in this RFC -This RFC commits us to adding *some* form of this syntax for `async` but defers the bikeshed for [RFC #3668][] or some future RFC. We refer to this future syntax `🚲async<$ty>`. Usage of this syntax should be equivalent to typing `impl Future`. Note that `🚲async<$ty>` is not a Rust type alias but a syntactic substitution, and therefore the `impl` keyword here has a different role depending on where it appears (in [function arguments][apit], it corresponds to a new generic argument to the fn; in [return position][rpit], it corresonds to an [abstract return type][rpit], etc). +This RFC commits us to adding *some* form of this syntax for `async` but defers the bikeshed for [RFC #3668][] or some future RFC. We refer to this future syntax `🏠async<$ty>`. Usage of this syntax should be equivalent to typing `impl Future`. Note that `🏠async<$ty>` is not a Rust type alias but a syntactic substitution, and therefore the `impl` keyword here has a different role depending on where it appears (in [function arguments][apit], it corresponds to a new generic argument to the fn; in [return position][rpit], it corresonds to an [abstract return type][rpit], etc). [apit]: https://doc.rust-lang.org/stable/reference/types/impl-trait.html#anonymous-type-parameters [rpit]: https://doc.rust-lang.org/stable/reference/types/impl-trait.html#abstract-return-types This RFC also recommends that future flavors which "transform" the result of a block or function introduce a corresponding syntax. -Where `🚲K<$ty>` exists it should meet the following invariants: +Where `🏠K<$ty>` exists it should meet the following invariants: -1. A `K`-function `K fn foo() -> $ty { $expr }` returns a value of type `🚲K<$ty>` when called. +1. A `K`-function `K fn foo() -> $ty { $expr }` returns a value of type `🏠K<$ty>` when called. * The body `$expr` produces/returns a value of the declared type `$ty`. -2. A `K`-block `K { $expr }` produces a result of type `🚲K<$ty>`, where: +2. A `K`-block `K { $expr }` produces a result of type `🏠K<$ty>`, where: * `$ty` is the type produced by the body `$expr` (or `break` statements that target this block). -3. A `K`-closure `K |$args| -> $ty { $expr }` returns a value of type `🚲K<$ty>` when called. +3. A `K`-closure `K |$args| -> $ty { $expr }` returns a value of type `🏠K<$ty>` when called. * The body `$expr` produces/returns a value of the declared type `$ty`. * The `K`-closure implements the trait `K Fn`. -4. There is some operation, called the "do" operation, that takes a `🚲K<$ty>` value and returns a `$ty` value to the surrounding code. +4. There is some operation, called the "do" operation, that takes a `🏠K<$ty>` value and returns a `$ty` value to the surrounding code. #### Example: `async` -* The definition of `🚲async<$ty>` is `impl Future`. +* The definition of `🏠async<$ty>` is `impl Future`. * The "do" operation for `async` is `await`. ### Relationship between `K`-flavored functions, blocks, and types -Looking at async functions and the proposed `🚲async<$ty>` notation, we observe the following relationship. An `async` function like `count_input`... +Looking at async functions and the proposed `🏠async<$ty>` notation, we observe the following relationship. An `async` function like `count_input`... ```rust async fn count_input(urls: &[Url]) -> usize { @@ -215,10 +215,10 @@ async fn count_input(urls: &[Url]) -> usize { } ``` -...can be transformed into an "ordinary" function that (a) returns `🚲async<$ty>` and (b) uses an `async move` block (caveat: the true transformation is slightly more subtle [in order to preserve the drop order for method arguments][droporder]): +...can be transformed into an "ordinary" function that (a) returns `🏠async<$ty>` and (b) uses an `async move` block (caveat: the true transformation is slightly more subtle [in order to preserve the drop order for method arguments][droporder]): ```rust -fn count_input(urls: &[Url]) -> 🚲async { +fn count_input(urls: &[Url]) -> 🏠async { async move { // ... iterate over urls and load data ... } @@ -229,9 +229,9 @@ This works because async blocks rewrite their content to produce a different val #### What is new in this RFC -This RFC recommends that future flavors that define `🚲K<$ty>` meet this invariant: +This RFC recommends that future flavors that define `🏠K<$ty>` meet this invariant: -* A `K fn foo() -> T` should be equivalent to a (unflavored) `fn` that returns `🚲async` and uses some form of `K`-flavored block. +* A `K fn foo() -> T` should be equivalent to a (unflavored) `fn` that returns `🏠async` and uses some form of `K`-flavored block. ## The "filter flavor" subset @@ -264,10 +264,10 @@ Continuing with the Runtime-Rust hypothetical, most parts of the flavor pattern There is one part however that doesn't really fit: -* `K`-flavored types can be written with the syntax `🚲K`: - * `K`-flavored functions, closures, and blocks return/produce `🚲K` values, where `T` is their original type. +* `K`-flavored types can be written with the syntax `🏠K`: + * `K`-flavored functions, closures, and blocks return/produce `🏠K` values, where `T` is their original type. -Introducing a `🚲runtime` syntax to describe a "runtime-flavored type" doesn't make sense, because `runtime` blocks don't change the type of value that is produced. So `🚲runtime = T` always. We don't need syntax for this and having it would be confusing. Furthermore, the equivalence between a `K`-function and a function whose body returns a `K`-block doesn't hold: +Introducing a `🏠runtime` syntax to describe a "runtime-flavored type" doesn't make sense, because `runtime` blocks don't change the type of value that is produced. So `🏠runtime = T` always. We don't need syntax for this and having it would be confusing. Furthermore, the equivalence between a `K`-function and a function whose body returns a `K`-block doesn't hold: * `runtime fn $name() -> T { $expr }` is NOT equivalent to `fn $name() -> T { runtime { $expr } }` @@ -284,11 +284,11 @@ Based on our discussion of `runtime`, we can identify the parts of the full flav * `K`-closures using the syntax `K [move] |$args| $expr` to define a K-closure; * Closures implement the `K Fn` traits. -The other parts of the pattern (e.g., the `🚲K` syntax or defining a "do" operation) do not apply because filter flavors don't change the type of the value produced by the block. Further, the transformation from `K fn $name` to a function with a `K`-block doesn't work, because the point of a `K fn $name()` is to signal to callers that `$name` can only be called inside of a `K`-block. +The other parts of the pattern (e.g., the `🏠K` syntax or defining a "do" operation) do not apply because filter flavors don't change the type of the value produced by the block. Further, the transformation from `K fn $name` to a function with a `K`-block doesn't work, because the point of a `K fn $name()` is to signal to callers that `$name` can only be called inside of a `K`-block. ## Teaching async via the "flavor pattern" -> The "flavor pattern" is meant to guide Rust designers as we extend the language, not to be something directly taught to end users. To illustrate how it might feel, this section covers an example of teaching `async` leveraging the syntax and concepts of the flavor pattern (but not teaching the pattern explicitly). To help illustrate where the overall vision for Rust, we assume that `🚲K<$T>` is `K -> $T` (a variant of [RFC #3628][] currently preferred by the author), that `async -> T` and its equivalent `impl Trait` are supported in local variable declarations ([RFC #2071][]), and that there is some form of `async Iterator` trait available in std. +> The "flavor pattern" is meant to guide Rust designers as we extend the language, not to be something directly taught to end users. To illustrate how it might feel, this section covers an example of teaching `async` leveraging the syntax and concepts of the flavor pattern (but not teaching the pattern explicitly). To help illustrate where the overall vision for Rust, we assume that `🏠K<$T>` is `K -> $T` (a variant of [RFC #3628][] currently preferred by the author), that `async -> T` and its equivalent `impl Trait` are supported in local variable declarations ([RFC #2071][]), and that there is some form of `async Iterator` trait available in std. ### Async functions @@ -509,13 +509,13 @@ Every flavor keyword `K` supports: Rewrite flavors further support: -* `K`-types using a TBD syntax, denoted `🚲K<$ty>` for now. - * A `K`-type `🚲K<$ty>` is the type resulting from a `K`-block whose expression has type `$ty`. +* `K`-types using a TBD syntax, denoted `🏠K<$ty>` for now. + * A `K`-type `🏠K<$ty>` is the type resulting from a `K`-block whose expression has type `$ty`. * A `K`-function `K fn $name($args) -> $ty { $expr }` is "roughly" equivalent to a regular function whose return type and body are `K`-flavored: - * `fn $name($args) -> 🚲K<$ty> { K { $expr } }` + * `fn $name($args) -> 🏠K<$ty> { K { $expr } }` * The translation will vary slightly depending on the specifics of the flavor. The goal is that `K`-functions should behave as close as possible to an unflavored function. For example, drop order should be preserved. -* A `K`-closure returns `🚲K<$ty>`, where `$ty` would have been the return type had the closure been unflavored. -* A "do" operation that, when executed in a `K`-block, consumes a `🚲K<$ty>` and produces a `$ty` value. +* A `K`-closure returns `🏠K<$ty>`, where `$ty` would have been the return type had the closure been unflavored. +* A "do" operation that, when executed in a `K`-block, consumes a `🏠K<$ty>` and produces a `$ty` value. Some flavors may only include a subset of these features. This subset should be documented and explained. @@ -551,9 +551,9 @@ During the RFC period we evaluated several possible names. The term "flavor" was **Color** was initially chosen as a playful allusion to the ["What color is your function"?][WCIF] blog post. Color is approachable but such a common word in conversation that readers may not realize it is a "term of art". It is also widely used in some domains, such as GUIs, making it a poor choice for keyword (not that we are proposing a keyword). Also, while color is relatively grammatically flexible, it can still be awkward. For example, we sometimes talk about a specific "flavor" of a trait, and saying "the async color of the Trait" doesn't sound right. There was also the problem that, as y'all are hopefully aware, the term "colored" has been associated with some awful parts of human history, and some common phrasings could have undesirable connotations. -## Why include `🚲K<$T>` syntax? +## Why include `🏠K<$T>` syntax? -Most parts of the flavor pattern already exist in Rust today or at least in accepted RFCs. The `🚲K<$T>` syntax for flavored types stands out as the exception. It was included in the RFC because it forms an important part of the overall story (witness how prominent it is in the guide section). Some members of the lang team felt that, without `🚲K<$T>`, they didn't feel good about the flavor pattern overall. +Most parts of the flavor pattern already exist in Rust today or at least in accepted RFCs. The `🏠K<$T>` syntax for flavored types stands out as the exception. It was included in the RFC because it forms an important part of the overall story (witness how prominent it is in the guide section). Some members of the lang team felt that, without `🏠K<$T>`, they didn't feel good about the flavor pattern overall. ## Why not use the camel-case named for K-flavored traits, like `AsyncFn` instead of `async Fn`? @@ -576,14 +576,14 @@ One advantage of today's sugar for trait bounds (`T: Fn()`, `T: FnMut()`, etc) i We deliberately chose non-standard terms to avoid associations. For example, the "filter" flavor might also be called an "effect-carrying flavor", but that invites debate as to the precise definition of an effect and whether `unsafe` qualifies. -## Can `🚲K<$T>` be used in closure return types like it can for regular functions? +## Can `🏠K<$T>` be used in closure return types like it can for regular functions? -The answer to this is complicated and left to be resolved by future RFCs that actually add the `🚲K<$T>` notation. Recall that we specified that `K`-flavors can be migrated from a `K`-function to its return type and body: +The answer to this is complicated and left to be resolved by future RFCs that actually add the `🏠K<$T>` notation. Recall that we specified that `K`-flavors can be migrated from a `K`-function to its return type and body: ```rust K fn foo() -> T { ... } // becomes -fn foo() -> 🚲K { K { ... } } +fn foo() -> 🏠K { K { ... } } ``` The question then is whether it is possible to transform a a `K`-closure in a similar way: @@ -591,19 +591,19 @@ The question then is whether it is possible to transform a a `K`-closure in a si ```rust K |args| -> T { ... } // could maybe become -|args| -> 🚲K { K { ... } } +|args| -> 🏠K { K { ... } } ``` -Supporting this notation is tricky however because closure return types are often elided. Given that `K`-closure types implement `K Fn` rather than the typical `Fn` trait, we need to be able to determine if a closure has flavor `K` to decide what traits it implements. But there is no way to distinguish whether an expression like `|| K { .. }` is meant to be a `K`-closure that implements `K Fn` or an unflavored closure that implements `Fn>` value. As described in [RFC #3668][], these two traits are not equivalent for `async` and likely not for other future rewrite flavors. +Supporting this notation is tricky however because closure return types are often elided. Given that `K`-closure types implement `K Fn` rather than the typical `Fn` trait, we need to be able to determine if a closure has flavor `K` to decide what traits it implements. But there is no way to distinguish whether an expression like `|| K { .. }` is meant to be a `K`-closure that implements `K Fn` or an unflavored closure that implements `Fn>` value. As described in [RFC #3668][], these two traits are not equivalent for `async` and likely not for other future rewrite flavors. We've left the behavior here to be specified in a future RFC, but we note that it would be simple to declare it as an error for now. Note that `-> impl Future` is also not accepted in this position. # Prior art [prior-art]: #prior-art -The term "flavor" is taken from the ["What flavor is your function?"][WCIF] blog post. As that post indicates, many languages have added flavors of one form or another. This RFC is focused on the syntactic patterns around flavors, specifically using the flavor as a prefix (e.g., `async fn`, `🚲async`, `async Trait`). This pattern of prefixing functions with flavors is very common and is very natural in English as the flavor plays the role of an adjective, describing the kind of function one has. +The term "flavor" is taken from the ["What flavor is your function?"][WCIF] blog post. As that post indicates, many languages have added flavors of one form or another. This RFC is focused on the syntactic patterns around flavors, specifically using the flavor as a prefix (e.g., `async fn`, `🏠async`, `async Trait`). This pattern of prefixing functions with flavors is very common and is very natural in English as the flavor plays the role of an adjective, describing the kind of function one has. -Although the RFC is focused on syntax, it does also suggest some equivalences that flavors must maintain, such as how a `K fn foo() -> T { expr }` can be transformed to `fn foo() -> 🚲K { K move { expr } }` for any rewrite flavor `K`. These equivalences hint at an underlying semantics for flavors. There are two related precedents in academia: +Although the RFC is focused on syntax, it does also suggest some equivalences that flavors must maintain, such as how a `K fn foo() -> T { expr }` can be transformed to `fn foo() -> 🏠K { K move { expr } }` for any rewrite flavor `K`. These equivalences hint at an underlying semantics for flavors. There are two related precedents in academia: * [Haskell]'s monads are comparable in some ways to rewrite flavors, and the [monadic laws][] suggest similar equivalences. A "[monad]" is like a flavor made up of two operations, one that "wraps" a value `v` to yield a `Wrapped` (analogous to `K-type!(v)`) and one that does an `and_then` operation, often called "fold", taking a `Wrapped`and a `V => Wrapped` closure and yielding a `Wrapped` value. The effect is kind of like a "programmable semicolon", allowing [Haskell] programs to introduce operations in in between each statement, and to potentially carry along "user data" with the wrapped `V` value. Haskell includes generic syntax ("do"-notation) designed to work with monads, allowing one to define generic functions that can be used with any monad `Wrapped`. * Monads are famously challenging to learn ("The sheer number of different monad tutorials on the internet is a good indication of the difficulty many people have understanding the concept", says the Haskell wiki). Flavors run the risk of being similarly complicated. However, this RFC is not introducing flavors as a concept that users would be taught, but rather as a syntactic pattern that they will pick up on as they learn about different existing parts of Rust (async, unsafe, etc). @@ -620,7 +620,7 @@ Although the RFC is focused on syntax, it does also suggest some equivalences th # Unresolved questions [unresolved-questions]: #unresolved-questions -## What syntax to use for `🚲K<$T>`? +## What syntax to use for `🏠K<$T>`? This question is purposefully left unresolved by this RFC and is meant to be addressed in follow-up RFCs, such as [RFC #3628][]. @@ -631,7 +631,7 @@ This question is purposefully left unresolved by this RFC and is meant to be add This RFC suggests a number of future changes to "fill out" the support for flavors: -* Async should add a `🚲async` syntax (see [RFC #3628][]). +* Async should add a `🏠async` syntax (see [RFC #3628][]). * We will eventually need a mechanism for defining async-flavored traits like `async Iterator` or `async Default`. For the short term this is not urgent as the traits we are focused on (such as `Fn` and `Drop`) are special. * Const-flavored traits should use a notation like (e.g.) `const Default`. * Unsafe-flavored traits like `unsafe Default`, which would mean a `Default` trait with an unsafe `default` method, are a possibility, but they could be easily confused for an unsafe trait. @@ -666,9 +666,9 @@ As described in [RFC #243][], a `try { $expr }` block executes `$expr` and "capt [#41414]: https://github.com/rust-lang/rust/issues/41414 [#70941]: https://github.com/rust-lang/rust/issues/70941 -Looking at `try` as a flavor, it is clear that ok-wrapping is the correct and consistent behavior. `try`, like `async`, is a fully general flavor that transforms its result type, and the flavor pattern specifies that the type of an expression in a block should be "ok-wrapped" to produce the block's final output type. Another point of controversy about `try` has been how to support it syntactically at the function level. This RFC suggests that the right answer is `try fn foo() -> T`, where `T` represents the "ok" type. The final result of calling `foo()` would therefore be `🚲try`, the ok-wrapped flavor of `T`. +Looking at `try` as a flavor, it is clear that ok-wrapping is the correct and consistent behavior. `try`, like `async`, is a fully general flavor that transforms its result type, and the flavor pattern specifies that the type of an expression in a block should be "ok-wrapped" to produce the block's final output type. Another point of controversy about `try` has been how to support it syntactically at the function level. This RFC suggests that the right answer is `try fn foo() -> T`, where `T` represents the "ok" type. The final result of calling `foo()` would therefore be `🏠try`, the ok-wrapped flavor of `T`. -While treating `try` as a flavor is appealing, it does have one important difference from `async`: `try` and `?` are intended to be usable with many types, including `Result`, `Option`, and others. In terms of the flavor pattern, it is therefore unclear what the syntax `🚲try` should expand to. +While treating `try` as a flavor is appealing, it does have one important difference from `async`: `try` and `?` are intended to be usable with many types, including `Result`, `Option`, and others. In terms of the flavor pattern, it is therefore unclear what the syntax `🏠try` should expand to. [`Try`]: https://doc.rust-lang.org/std/ops/trait.Try.html @@ -693,19 +693,19 @@ try fn load_configuration_string() -> String { } ``` -This would be equivalent to the following unflavored function returning `🚲try`: +This would be equivalent to the following unflavored function returning `🏠try`: ```rust use std::io::try; -fn load_configuration_string() -> 🚲try { +fn load_configuration_string() -> 🏠try { try { std::fs::read_to_string("config.txt")? } } ``` This is only one possibility. There are several other ideas for how try could be integrated: -* Define `type try = Result` as an alias for the residual and use `try -> T` to mean `impl Try` (this assumes `🚲try` is defined as `try -> T`). +* Define `type try = Result` as an alias for the residual and use `try -> T` to mean `impl Try` (this assumes `🏠try` is defined as `try -> T`). * Extend `try fn` with a clause like `try fn foo() -> T throws X` and have it expand to `impl Try` or something like that. ### `gen` as a rewrite flavor @@ -742,9 +742,9 @@ gen fn all() yields u32 { } ``` -Treating `gen` as a `flat_map` operation does not map to the "do" operation defined in the flavor pattern. The "do" operation is meant to take a transformed value of type `🚲gen` and produce a single value of type `T`; but `flat_map` produces any number of `T` values. This is not necessarily a problem, not all flavors have to provide all parts of the pattern, but it would suggest that `gen` is a distinct category of flavor from a rewrite flavor like `async` or `try`. +Treating `gen` as a `flat_map` operation does not map to the "do" operation defined in the flavor pattern. The "do" operation is meant to take a transformed value of type `🏠gen` and produce a single value of type `T`; but `flat_map` produces any number of `T` values. This is not necessarily a problem, not all flavors have to provide all parts of the pattern, but it would suggest that `gen` is a distinct category of flavor from a rewrite flavor like `async` or `try`. -There is however another "do" operation we might use for generators which *does* fit the rewrite pattern, but which may not be as intuitive or useful to end users. In addition to the yield type that we have been discussing, generators can be extended with the idea of a final value type; we can use the syntax `🚲gen`, where `Y` is the *yielding* type and `F` is the *final* type that is returned. (An existing `impl Iterator` can be seen as having a yield type of `I` and a final type of `()`.) In this case, the "do" operation would be `yield_all`, which would yield up all `Y` items and then return the final `F` value. +There is however another "do" operation we might use for generators which *does* fit the rewrite pattern, but which may not be as intuitive or useful to end users. In addition to the yield type that we have been discussing, generators can be extended with the idea of a final value type; we can use the syntax `🏠gen`, where `Y` is the *yielding* type and `F` is the *final* type that is returned. (An existing `impl Iterator` can be seen as having a yield type of `I` and a final type of `()`.) In this case, the "do" operation would be `yield_all`, which would yield up all `Y` items and then return the final `F` value. Here is an example using the hypothesized "yield all" construct, showing how you could work with `gen` as a flavor. In this case, the `yield_all` 'unwraps' the generator effect, propagating the yielded values, and returning the final value. Calling `outer` would then yield up [`0`, `1`, `2`, `6`, `7`, `8`] and return 42. Note how `.yield_all` appears at the same places that `.await` would appear if these were async functions: From 76736f101f0bb99a7ab9f0ff401eda05bddfb07e Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 15 Oct 2024 14:55:39 -0400 Subject: [PATCH 08/11] explain the house emoji --- text/0000-flavor-syntactic-design-pattern.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-flavor-syntactic-design-pattern.md b/text/0000-flavor-syntactic-design-pattern.md index 12afd8128de..a5aa2cb3742 100644 --- a/text/0000-flavor-syntactic-design-pattern.md +++ b/text/0000-flavor-syntactic-design-pattern.md @@ -29,7 +29,7 @@ In the flavor pattern, each flavor is tied to a specific keyword `K`. Flavors sh Some flavors rewrite code so that it executes differently (e.g., `async`). These are called **rewrite flavors**. Each such flavor should have the following: * A syntax `🏠K<$ty>` defining the `K`-type, the type that results from a `K`-block, `K`-function, or `K`-closure whose body has type `$ty`. - * The `🏠K<$ty>` is a placeholder. We expect a future RFC to define the actual syntax. + * The `🏠K<$ty>` is a placeholder. We expect a future RFC to define the actual syntax. (The 🏠 emoji is meant to symbolize a "bikeshed".)c * A "do" operation that, when executed in a `K`-block, consumes a `🏠K<$ty>` and produces a `$ty` value. * The property that a `K`-function can be transformed to a regular function with a `K`-flavored return type and body. * i.e., the following are roughly equivalent (the precise translation can vary so as to e.g. preserve drop order): From 0719eae4aaaebfc89c11488639e77a5aa5ed2759 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 15 Oct 2024 15:34:13 -0400 Subject: [PATCH 09/11] add a FAQ about mechanical translation and talk about the "just add flavor" goal --- text/0000-flavor-syntactic-design-pattern.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/text/0000-flavor-syntactic-design-pattern.md b/text/0000-flavor-syntactic-design-pattern.md index a5aa2cb3742..ea4b3553a9f 100644 --- a/text/0000-flavor-syntactic-design-pattern.md +++ b/text/0000-flavor-syntactic-design-pattern.md @@ -79,6 +79,10 @@ Looking more closely at `async` and `const`, we see that there is a latent conce * `unsafe` functions can only be called from `unsafe` blocks or other `unsafe` functions. * `const` functions can only call other `const` functions. +## Goal: adopting a flavor can be done by "just adding the keyword" + +The goal for all Rust flavors is to be consistent with unflavored Rust overall. Generally speaking, adding a flavor to some code should just require adding the flavor keyword in the right places, along with other complementary keywords (e.g., `await` for `async`). One goal of this RFC is to outline all the places that flavors must be supported to enable this "just add flavor" feel. We do this by exploring the example of `async` (and then exploring `const`, which requires a subset of those places). + ## Tenet: consistent syntax and transformations makes Rust easier to learn The premise of this RFC is that flavor keywords like `const`, `async`, and `unsafe` "feel similar" to users and that we should make them work as consistently as possible (but no more than that). Because they serve different purposes, we don't wish to force them into a single shape if that shape is an ill-fit. Nonetheless, we wish to ensure that these flavors, and any future "flavor-like" features, feel as consistent and predictable as possible, so that users can develop "muscle memory" that helps them to predict syntax they haven't used or seen yet. The RFC describes the syntax to use for a flavor keyword applied to various constructs as well as some of the "approximate equivalences" that flavors should generally hold. @@ -89,7 +93,7 @@ Code with a given flavor `K` is meant to interoperate fully with other code with Existing flavors do not yet support all the aspects described herein. This RFC recommends changes that close a limited number of these gaps; the [Future Possibilities](#future-possibilities) discusses other changes we could make to more fully support the flavor pattern for every flavor. -## Tenet: all parts of the flavor pattern should have explicit syntax centered on the keyword `K` +## Tenet: flavors should be understandable on their own One of the recommendations of this RFC is committing to add a `K`-flavored type syntax in the future, similar to what is proposed in [RFC #3628][]. The motivation for this is that it is important when teaching flavors to be able to teach the *flavor specifically*, without having to reference additional language features like `impl Trait`. This addresses feedback we have received from Rust trainers which is that one of the difficulties in teaching Rust is that users can't learn Rust in "layers", they have to learn all parts of Rust at once before any of it makes sense. @@ -528,6 +532,14 @@ Answer: nothing. It's just an observation of fact! `K` is a keyword that can be Longer answer: it suggests that `K` should support the syntaxes and operations described in this RFC. Each flavor is different, so it is possible that a given piece of syntax is not relevant, but you ought to be able to explain why. Certainly past experience with flavors suggests that over time you will want to be able to use them in all the places identified in the flavor pattern to allow people to write code naturally.g +## Will there be a mechanical connection between flavors of a trait (like `async Read`) and the original (`Read`)? + +That is not yet clear. The only flavored trait currently RFC'd is the `async` flavor of the `Fn` trait. It is not clear whether we will ever add an `async` flavor for the `Read` trait or what language mechanism we would use to do so. It could be automatically derived from `Read` but it could also be a divergent definition. + +However, the RFC does give some guidance for flavored traits. Specifically, it says that a flavored trait should offer *at least* the same members as the unflavored version, some of which may themselves now be flavored. The `async FnOnce` trait is a good example of this. Like the ordinary `FnOnce` trait, it offers a `call_once` method (but flavored `async`) and an `Output` associated type. As an implementation detail, it has additional members, like the `CallOnceFuture`; these are not currently exposed to users but they could be in the future. + +The reason that we specify that flavored traits have *at least* the same members as unflavored ones is to preserve the invariant that unflavored code can be ported to a flavor by "just adding the flavor keyword". If there is existing unflavored code using the trait, you should be able to "flavor" it easily. This would not be possible if the flavored version of the trait lacked some of the features or mechanisms of the original (unless of course the existence of those features is directly in contradiction with the purpose of the flavor, e.g., a panic-free flavor would not include a panic-free flavored method that is guaranteed to panic). + ## What about `mut`, is that a flavor? The `mut` keyword does not qualify as a flavor per the definition in this RFC because it cannot be applied to functions or blocks. As such, it's not clear how one would extend `mut` to apply to other locations like traits or closures. From a064addd58fe00664d606c8e4c89c4c53485333f Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 15 Oct 2024 15:55:01 -0400 Subject: [PATCH 10/11] add a camel-case but async Fn FAQ --- text/0000-flavor-syntactic-design-pattern.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/0000-flavor-syntactic-design-pattern.md b/text/0000-flavor-syntactic-design-pattern.md index ea4b3553a9f..93b56246f71 100644 --- a/text/0000-flavor-syntactic-design-pattern.md +++ b/text/0000-flavor-syntactic-design-pattern.md @@ -577,6 +577,10 @@ We considered a number of alternatives to `K Trait` for denoting K-flavored trai Ultimately, the killer argument was that this felt like everybody's preferred "second choice option", but nobody's *favorite*. It's not a bold design. +## Why not use camel-case names for most traits but `async Fn` for the fn traits? + +The concern is that typing the name `Fn` when, in fact, the trait is called `AsyncFn` seemed like to confuse users. Morever, it is not building a muscle memory that can be applied to other traits. (On the other hand, the function traits are already quite built-in and have significant "language syntax" associated with them, like custom `Fn()` notation in bounds and closure expressions.) + ## Why not use a notation like `T: async fn()` instead of `T: async Fn()`? One advantage of today's sugar for trait bounds (`T: Fn()`, `T: FnMut()`, etc) is that it more closely resembles function declarations. Adding the async keyword continues that, in that one now puts `async` in front of the `fn` or closure and then likewise in front of the bound (`T: async Fn()`). Iterating on that vein, we considered going further with bound notation, so that one would write `T: fn()` instead of `T: Fn()` and then `T: async fn()` instead of `T: async Fn()`. The following objections were raised: From e14edafdff2b27a4001398a05300431f17cb61f1 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 15 Oct 2024 16:20:46 -0400 Subject: [PATCH 11/11] expand the unanswered questions --- text/0000-flavor-syntactic-design-pattern.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/text/0000-flavor-syntactic-design-pattern.md b/text/0000-flavor-syntactic-design-pattern.md index 93b56246f71..4e260e4d397 100644 --- a/text/0000-flavor-syntactic-design-pattern.md +++ b/text/0000-flavor-syntactic-design-pattern.md @@ -640,6 +640,13 @@ Although the RFC is focused on syntax, it does also suggest some equivalences th This question is purposefully left unresolved by this RFC and is meant to be addressed in follow-up RFCs, such as [RFC #3628][]. +## How will flavored traits work exactly (apart from `async Fn`)? + +This question is purposefully left unresolved by this RFC. Let's use `async std::io::Read` as an example. The RFC is not saying that `async Read` should be mechanically derived from `Read` (see also [this FAQ](#will-there-be-a-mechanical-connection-between-flavors-of-a-trait-like-async-read-and-the-original-read)). What the RFC does say is that we *intend* to support flavors and that future work will need to be done to elaborate those implications. There are some minimum requirements: + +* You should think of `async $Trait` as the *async flavor of `$Trait`*, which means that you import the "unflavored trait name" (e.g., `use std::io::Read`) and then apply the `async` keyword to it. +* The methods/items available in the trait should should be a superset of the unflavored version, some of which may be "flavored" (e.g., `async Read` has the same methods as `Read`, but some of them are `async`; it may offer additional methods to account for extra functionality). + # Future possibilities [future-possibilities]: #future-possibilities