├── .gitignore ├── FAQ.md ├── archive ├── README.md └── evaluation │ ├── effects-in-rust │ ├── sized.md │ ├── pin.md │ ├── send.md │ ├── unsafe.md │ ├── panic.md │ ├── ownership.md │ ├── try.md │ ├── const.md │ ├── async.md │ └── gen.md │ ├── README.md │ ├── effects-in-rust.md │ ├── introducing-new-keyword-generics.md │ ├── effect-hierarchy.md │ ├── overloading-keyword-generics.md │ ├── mir-desugaring.md │ ├── prior-art.md │ ├── keyword-conditions.md │ ├── grouping-keyword-generics.md │ └── what-to-support.md ├── evaluation ├── syntax │ ├── README.md │ ├── _template.md │ ├── postfix-question-mark.md │ ├── attributes.md │ ├── effect-as-a-clause.md │ ├── where-effect-bounds.md │ └── const-bool-like-effects.md ├── README.md ├── unleakable-types.md ├── auto-concurrency.md └── pattern-types.md ├── meetings └── README.md ├── CODE_OF_CONDUCT.md ├── .github ├── ISSUE_TEMPLATE │ ├── question.md │ ├── proposal.md │ ├── bug-report.md │ └── experience-report.md └── workflows │ └── deploy_mdbook.yml ├── explainer ├── README.md └── effect-generic-trait-declarations.md ├── updates ├── README.md ├── template.md ├── 2023-02-23-keyword-generics-progress-report-feb-2023.md └── 2022-07-27-announcing-the-keyword-generics-initiative.md ├── book.toml ├── LICENSE-MIT ├── SUMMARY.md ├── README.md ├── CHARTER.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /book 2 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # 😕 FAQ 2 | -------------------------------------------------------------------------------- /archive/README.md: -------------------------------------------------------------------------------- 1 | # Archive 2 | -------------------------------------------------------------------------------- /evaluation/syntax/README.md: -------------------------------------------------------------------------------- 1 | # Syntax 2 | -------------------------------------------------------------------------------- /meetings/README.md: -------------------------------------------------------------------------------- 1 | # Meetings 2 | 3 | This folder contains the minutes of all the recorded meetings that have happened 4 | so far. 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # The Rust Code of Conduct 2 | 3 | The Code of Conduct for this repository [can be found online](https://www.rust-lang.org/conduct.html). 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask for clarification about some aspect of the design 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please describe what you want to know! -------------------------------------------------------------------------------- /archive/evaluation/effects-in-rust/sized.md: -------------------------------------------------------------------------------- 1 | # Object Safety (Data-Type Effect) 2 | 3 | ### Asynchrony 4 | ### Compile-time Execution 5 | ### Fallibility 6 | ### Iteration 7 | ### Unwinding 8 | ### Memory-Safety 9 | ### Immovability 10 | ### Object-Safety 11 | ### Ownership 12 | ### Thread-Safety 13 | -------------------------------------------------------------------------------- /archive/evaluation/effects-in-rust/pin.md: -------------------------------------------------------------------------------- 1 | # Immovability (Data-Type Effect) 2 | 3 | ## Interactions with other effects 4 | 5 | ### Asynchrony 6 | ### Compile-time Execution 7 | ### Fallibility 8 | ### Iteration 9 | ### Unwinding 10 | ### Memory-Safety 11 | ### Immovability 12 | ### Object-Safety 13 | ### Ownership 14 | ### Thread-Safety 15 | -------------------------------------------------------------------------------- /archive/evaluation/effects-in-rust/send.md: -------------------------------------------------------------------------------- 1 | # Thread-Safety (Data-Type Effect) 2 | 3 | ## Interactions with other effects 4 | 5 | ### Asynchrony 6 | ### Compile-time Execution 7 | ### Fallibility 8 | ### Iteration 9 | ### Unwinding 10 | ### Memory-Safety 11 | ### Immovability 12 | ### Object-Safety 13 | ### Ownership 14 | ### Thread-Safety 15 | -------------------------------------------------------------------------------- /explainer/README.md: -------------------------------------------------------------------------------- 1 | # 📚 Draft RFCs 2 | 3 | > The "Draft RFCs" are "end-user readable" documentation that explains how to use the feature being deveoped by this initiative. 4 | > If you want to experiment with the feature, you've come to the right place. 5 | > Until the feature enters "feature complete" form, the Draft RFCs should be considered a work-in-progress. 6 | -------------------------------------------------------------------------------- /archive/evaluation/effects-in-rust/unsafe.md: -------------------------------------------------------------------------------- 1 | # Memory Safety (Scoped Effect) 2 | 3 | ### Asynchrony 4 | ### Compile-time Execution 5 | ### Fallibility 6 | ### Iteration 7 | ### Unwinding 8 | ### Memory-Safety 9 | ### Immovability 10 | ### Object-Safety 11 | ### Ownership 12 | ### Thread-Safety 13 | 14 | ## References 15 | 16 | - [Keywords I: Unsafe Syntax](https://blog.yoshuawuyts.com/unsafe-syntax/) 17 | -------------------------------------------------------------------------------- /evaluation/README.md: -------------------------------------------------------------------------------- 1 | # 🔬 Evaluation 2 | 3 | > The *evaluation* surveys the various design approaches that are under consideration. 4 | It is not required for all initiatives, only those that begin with a problem statement 5 | but without a clear picture of the best solution. Often the evaluation will refer to topics 6 | in the [design-discussions] for more detailed consideration. 7 | 8 | [design-discussions]: ./design-discussions.md -------------------------------------------------------------------------------- /archive/evaluation/README.md: -------------------------------------------------------------------------------- 1 | # 🔬 Evaluation 2 | 3 | > The *evaluation* surveys the various design approaches that are under consideration. 4 | It is not required for all initiatives, only those that begin with a problem statement 5 | but without a clear picture of the best solution. Often the evaluation will refer to topics 6 | in the [design-discussions] for more detailed consideration. 7 | 8 | [design-discussions]: ./design-discussions.md -------------------------------------------------------------------------------- /updates/README.md: -------------------------------------------------------------------------------- 1 | # ✏️ Updates 2 | 3 | Lang-team initiatives give monthly updates. This section collects the updates from this initiative for posterity. 4 | 5 | To add a new update: 6 | 7 | * Create a new file `updates/YYYY-mmm.md`, e.g. `updates/2021-nov.md` 8 | * We recomend basing this on the [update template] 9 | * Link it from the `SUMMARY.md` 10 | 11 | [update template]: https://github.com/rust-lang/initiative-template/tree/master/updates/template.md -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["keyword generics Members"] 3 | language = "en" 4 | multilingual = false 5 | src = "." 6 | title = "keyword generics initiative" 7 | 8 | [output.html] 9 | no-section-label=true 10 | git-repository-url="https://github.com/yoshuawuyts/keyword-generics-initiative" 11 | edit-url-template="https://github.com/yoshuawuyts/keyword-generics-initiative/edit/master/{path}" 12 | site-url="/keyword-generics-initiative/" 13 | 14 | [output.html.fold] 15 | enable = true 16 | level = 0 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/proposal.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Proposal 3 | about: Propose a change to the way this feature works 4 | title: '' 5 | labels: proposal 6 | assignees: '' 7 | 8 | --- 9 | 10 | **When proposing changes, please check the FAQ and design questions to see if this idea has already been considered. It's also a good idea to check with the owner first.** 11 | 12 | ### Motivation 13 | 14 | Describe what you're trying to accomplish -- what problem are you trying to solve? 15 | 16 | ### Details 17 | 18 | Describe your idea. 19 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug using this feature 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ### When using 10 | 11 | Please indicate the precise nightly build you are using; try `rustc --version` 12 | 13 | ### What I tried to do 14 | 15 | Please include sample code or links to your project! 16 | 17 | ### What happened 18 | 19 | Describe what happened in as much detail as possible. 20 | For compiler ICEs, a good idea would be to include the output with `RUST_BACKTRACE=1`. 21 | 22 | ### What I expected 23 | 24 | Describe what you thought should happen. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/experience-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Experience report 3 | about: Describe your experiences using this feature 4 | title: '' 5 | labels: experience-report 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Code description 11 | 12 | Describe what you were trying to do with generic associated types. Links to source code are always appreciated! 13 | 14 | ### What worked well 15 | 16 | Describe what aspects of GATs worked well for you. 17 | 18 | ### What worked less well 19 | 20 | Describe what aspects of GATs did not work well for you. Perhaps they were confusing, or you weren't able to get your code to compile the way you wanted? Or perhaps it was just not ergonomic. 21 | -------------------------------------------------------------------------------- /.github/workflows/deploy_mdbook.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-20.04 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Setup mdBook 17 | uses: peaceiris/actions-mdbook@v1 18 | with: 19 | mdbook-version: '0.4.10' 20 | # mdbook-version: 'latest' 21 | 22 | - run: mdbook build 23 | 24 | - name: Deploy 25 | uses: peaceiris/actions-gh-pages@v3 26 | if: ${{ github.ref == 'refs/heads/master' }} 27 | with: 28 | github_token: ${{ secrets.GITHUB_TOKEN }} 29 | publish_dir: ./book 30 | -------------------------------------------------------------------------------- /updates/template.md: -------------------------------------------------------------------------------- 1 | # YYYY-MMM: Lang team update 2 | 3 | ## Summary 4 | 5 | * Summarizes the key points here! 6 | * These should be in a format suitable to be copied-and-pasted into a "lang team monthly updates" blog post. 7 | * This is great place to give a shout out to contributors. 8 | 9 | ## Goals for this month 10 | 11 | * Outline the things you would like to achieve this month 12 | * Best is if you can make these fairly concrete: 13 | * "Extend evaluation doc to cover XXX" 14 | * "Find a solution for #12345" 15 | 16 | ## Questions for discussion, meeting proposals 17 | 18 | * Outline anything where you would like feedback from the broader lang team or community 19 | 20 | ## (Other topic) 21 | 22 | * Add additional `##` headings for topics that you'd like to cover in more detail. 23 | * For example, you could add a section to dive into details from the summary, or to explain a new approach that you decided to take (and why). 24 | * Please include links into the explainers, evaluation, github issues, etc where applicable! -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /archive/evaluation/effects-in-rust/panic.md: -------------------------------------------------------------------------------- 1 | # Unwinding (Scoped Effect) 2 | 3 | ## Feature Status 4 | 5 | todo 6 | 7 | ## Description 8 | 9 | todo 10 | 11 | ## Refinements 12 | 13 | | Modifier | Description | 14 | | -------- | ----------- | 15 | 16 | The `panic` effect currently has no refinements. 17 | 18 | ## Feature categorization 19 | 20 | | Position | Syntax | 21 | | ----------- | -------------------------- | 22 | | Effect | N/A | 23 | | Yield | `panic!` | 24 | | Apply | `foo()` / `resume_unwind` | 25 | | Consume | `catch_unwind` / `fn main` | 26 | | Reification | N/A | 27 | 28 | Panics differ from all other control-flow oriented effects because every 29 | function is assumed to potentially panic. This means that the syntax to forward 30 | a panic from a function is just a regular function call. Panics are not 31 | represented in the type system, instead they exist as a property _outside_ of 32 | it. 33 | 34 | ## Interactions with other effects 35 | 36 | ### Asynchrony 37 | ### Compile-time Execution 38 | ### Fallibility 39 | ### Iteration 40 | ### Unwinding 41 | ### Memory-Safety 42 | ### Immovability 43 | ### Object-Safety 44 | ### Ownership 45 | ### Thread-Safety 46 | -------------------------------------------------------------------------------- /archive/evaluation/effects-in-rust.md: -------------------------------------------------------------------------------- 1 | # Effects in Rust 2 | 3 | Rust has a number of built-ins which sure look a lot like effects. In this 4 | section we cover what those are, how they're in use today, touch on some of the 5 | pain-points experienced by them. 6 | 7 | ## What do we mean by "effect" in this section? 8 | 9 | For the purpose of this section we're considering effects in the broadest terms: 10 | "Any built-in language mechanism which triggers a bifurcation of the design 11 | space". This means: anything which causes you to create a parallel, alternate 12 | copy of the same things is considered an effect in this space. 13 | 14 | This is probably not the definition we'll want to use in other sections, since 15 | effects should probably only ever apply to functions. In this section we're 16 | going to use "effect" as a catch-all term for "things that sure seem effect-y". 17 | When discussing effects we'll differentiate between: 18 | 19 | - **Scoped Effects**: which are effects which apply to functions and scopes, such 20 | as `async fn` which are reified as traits or types such as `impl Iterator`. 21 | - **Data-Type Effects**: which are Effects which apply to data types, encoded as 22 | auto-traits. For example: the `Send` auto-trait is automatically implemented on structs 23 | as long as its contained types are `Send`, and marks it as "thread-safe". 24 | -------------------------------------------------------------------------------- /evaluation/syntax/_template.md: -------------------------------------------------------------------------------- 1 | - Name: (fill me in: `name-of-design`) 2 | - Proposed by: [@name](link to github profile) 3 | - Original proposal (optional): (url) 4 | 5 | # Design 6 | 7 | 9 | 10 | ## base (reference) 11 | 12 | 14 | 15 | ```rust 16 | /// A trimmed-down version of the `std::Iterator` trait. 17 | pub trait Iterator { 18 | type Item; 19 | fn next(&mut self) -> Option; 20 | fn size_hint(&self) -> (usize, Option); 21 | } 22 | 23 | /// An adaptation of `Iterator::find` to a free-function 24 | pub fn find(iter: &mut I, predicate: P) -> Option 25 | where 26 | I: Iterator + Sized, 27 | P: FnMut(&T) -> bool; 28 | ``` 29 | 30 | ## always async 31 | 32 | 33 | 34 | ```rust 35 | // fill me in 36 | ``` 37 | 38 | ## maybe async 39 | 40 | 41 | 42 | ```rust 43 | // fill me in 44 | ``` 45 | 46 | ## generic over all modifier keywords 47 | 48 | 50 | 51 | ```rust 52 | // fill me in 53 | ``` 54 | 55 | # Notes 56 | 57 | 59 | -------------------------------------------------------------------------------- /archive/evaluation/introducing-new-keyword-generics.md: -------------------------------------------------------------------------------- 1 | # Adding new keyword generics 2 | 3 | ```rust 4 | const fn foo() { // maybe const context 5 | let file = fs::open("hello").unwrap(); 6 | // compile error! => `fs::open` is not a maybe const function! 7 | } 8 | 9 | ~base fn foo() { // assume `const` as the default; invert the relationship 10 | let file = fs::open("hello").unwrap(); 11 | // compile error! => `fs::open` is 12 | // a base function which cannot be 13 | // called from a maybe base context 14 | } 15 | 16 | ~async fn foo() { 17 | let file = my_definitely_async_fn().await; 18 | // compile error! 19 | } 20 | ``` 21 | 22 | ```rust 23 | fn foo(f: impl F * Fn() -> ()) { 24 | f(); 25 | } 26 | fn foo(f: impl effect Fn() -> ()) { 27 | f(); 28 | } 29 | 30 | // compile error! 31 | // effect `F` is maximally inclusive! 32 | // missing `.await` 33 | 34 | // maximally inclusive effects are not forward compatible! - once 35 | // we add a new effect existing code will not compile! 36 | // The calling convention may change each time we add a new effect! 37 | 38 | fn main() { 39 | foo(some_fn); // Infer all effects to Not* 40 | } 41 | ``` 42 | 43 | Adding new effects to the language does not break anyone, because effects must 44 | be opted in. Adding a new effect to the opt-in effect generics of a function 45 | will break callers that infer the effect to be required. 46 | 47 | Editions can add new effects to the list of defaults. This is not a breaking 48 | change because calling crates can stay on old editions, even if the lib crate 49 | got updated to a newer edition. THe lower edition crates don't see the defaults 50 | and turn them off. 51 | -------------------------------------------------------------------------------- /archive/evaluation/effects-in-rust/ownership.md: -------------------------------------------------------------------------------- 1 | # Ownership (Data-Type Effect) 2 | 3 | ## Description 4 | 5 | ## Feature Status 6 | 7 | ## Feature categorization 8 | 9 | | Position | Syntax | 10 | | ----------- | ------ | 11 | | Effect | | 12 | | Yield | | 13 | | Apply | | 14 | | Consume | | 15 | | Reification | | 16 | 17 | ## Positions Available 18 | 19 | | Position | Available | Example | 20 | | ------------------- | --------- | ---------------------------------- | 21 | | Manual trait impl | ✅ | `impl Future for Cat {}` | 22 | | Free functions | ✅ | `async fn meow() {}` | 23 | | Inherent functions | ✅ | `impl Cat { async fn meow() {} } ` | 24 | | Trait methods | ⏳ | `trait Cat { async fn meow() {} }` | 25 | | Trait declarations | ❌ | `async trait Cat {}` | 26 | | Block scope | ✅ | `fn meow() { async {} }` | 27 | | Argument qualifiers | ❌ | `fn meow(cat: impl async Cat) {}` | 28 | | Data types † | ❌ | `async struct Cat {}` | 29 | | Drop † | ❌ | `impl async Drop for Cat {}` | 30 | | Closures | ❌ | `async ǀǀ {}` | 31 | | Iterators | ❌ | `for await cat in cats {}` | 32 | 33 | ## Refinements 34 | 35 | | Modifier | Description | 36 | | -------- | ----------- | 37 | 38 | ## Interactions with other effects 39 | 40 | ### Asynchrony 41 | ### Compile-time Execution 42 | ### Fallibility 43 | ### Iteration 44 | ### Unwinding 45 | ### Memory-Safety 46 | ### Immovability 47 | ### Object-Safety 48 | ### Ownership 49 | ### Thread-Safety 50 | 51 | ## References 52 | 53 | - [Keywords II: Const Syntax](https://blog.yoshuawuyts.com/const-syntax/) 54 | -------------------------------------------------------------------------------- /archive/evaluation/effects-in-rust/try.md: -------------------------------------------------------------------------------- 1 | # Fallibility (Scoped Effect) 2 | 3 | ## Feature Status 4 | 5 | todo 6 | 7 | ## Description 8 | 9 | todo 10 | 11 | ## Refinements 12 | 13 | | Modifier | Description | 14 | | ------------------- | ----------------------------------------------------- | 15 | | `Option` | Used to describe optional values | 16 | | `Result` | Used to describe errors or success values | 17 | | `ControlFlow` | Used to represent control-flow loops | 18 | | `Poll` | Used to describe the state of `Future` state machines | 19 | 20 | While the reification of the fallibility effect in bounds ought to be `impl 21 | Try`, it more commonly is the case that we see concrete types used. 22 | 23 | ## Feature categorization 24 | 25 | | Position | Syntax | 26 | | ----------- | ----------------------- | 27 | | Effect | `try` | 28 | | Yield | `throw` | 29 | | Apply | `?` | 30 | | Consume | `match` / `fn main()` † | 31 | | Reification | `impl Try` | 32 | 33 | > † `fn main` implements effect polymorphism over the fallibility effect 34 | > by making use of the [`Termination` trait]. It stands to reason that _if_ we 35 | > had a `try` notation for functions, that it should be possible to write 36 | > `try fn main` which desugars to a `Result` type being returned. 37 | 38 | [`Termination` trait]: https://doc.rust-lang.org/std/process/trait.Termination.html 39 | 40 | 41 | ## Interactions with other effects 42 | 43 | ### Asynchrony 44 | ### Compile-time Execution 45 | ### Fallibility 46 | ### Iteration 47 | ### May Panic 48 | ### Memory-Unsafety 49 | ### Must-not Move 50 | ### Object-Safety 51 | ### Ownership 52 | ### Thread-Safety 53 | -------------------------------------------------------------------------------- /archive/evaluation/effect-hierarchy.md: -------------------------------------------------------------------------------- 1 | # Implications of the effect hierarchy 2 | 3 | One implication of the subset-superset relationship is that code which is 4 | generic over effects will not be able to use all functionality of the superset 5 | in the subset case. Though it will need to use the _syntax_ of the superset. 6 | 7 | Take for examle the following code. It takes two async closures, awaits them, 8 | and sums them: 9 | 10 | ```rust 11 | // Sum the output of two async functions: 12 | ~async fn sum( 13 | lhs: impl ~async FnMut() -> T, 14 | rhs: impl ~async FnMut() -> T 15 | ) -> T { 16 | let lhs = lhs().await; 17 | let rhs = rhs().await; 18 | lhs + rhs 19 | } 20 | ``` 21 | 22 | One of the benefits of async execution is that we gain _ad-hoc concurrency_, so 23 | we might be tempted to perform the comptutation of `lhs` and `rhs` concurrently, 24 | and summing the output once both have completed. However this should not be 25 | possible solely using effect polymorphism since the generated code needs to work 26 | in both async and non-async contexts. 27 | 28 | 29 | ```rust 30 | // Sum the output of two async functions: 31 | ~async fn sum( 32 | lhs: impl ~async FnMut() -> T, 33 | rhs: impl ~async FnMut() -> T 34 | ) -> T { 35 | let (lhs, rhs) = (lhs(), rhs()).join().await; 36 | // ^^^^^^^ 37 | // error: cannot call an `async fn` from a `~async` context 38 | // hint: instead of calling `join` await the items sequentially 39 | // or consider writing an overload instead 40 | lhs + rhs 41 | } 42 | ``` 43 | 44 | And this is not unique to `async`: in maybe-`const` contexts we cannot call 45 | functions from the super-context ("base Rust") since those cannot work during 46 | `const` execution. This leads to the following implication: __Conditional effect 47 | implementations require the syntactic annotations of the super-context, but 48 | cannot call functions which exclusively work in the super-context.__ 49 | -------------------------------------------------------------------------------- /evaluation/syntax/postfix-question-mark.md: -------------------------------------------------------------------------------- 1 | - Name: `postfix-question-mark` 2 | - Proposed by: [@tvallotton](https://github.com/tvallotton) 3 | 4 | # Design 5 | 6 | ## base (reference) 7 | 8 | 10 | 11 | ```rust 12 | /// A trimmed-down version of the `std::Iterator` trait. 13 | pub trait async? Iterator { 14 | type Item; 15 | async? fn next(&mut self) -> Option; 16 | fn size_hint(&self) -> (usize, Option); 17 | } 18 | 19 | /// An adaptation of `Iterator::find` to a free-function 20 | pub async? fn find(iter: &mut I, predicate: P) -> Option 21 | where 22 | I: async? Iterator + Sized, 23 | P: FnMut(&T) -> bool; 24 | ``` 25 | 26 | ## always async 27 | 28 | 29 | 30 | ```rust 31 | /// An adaptation of `Iterator::find` to a free-function 32 | pub async fn find(iter: &mut I, predicate: P) -> Option 33 | where 34 | I: async Iterator + Sized, 35 | P: FnMut(&T) -> bool; 36 | ``` 37 | 38 | ## maybe async 39 | 40 | 41 | 42 | ```rust 43 | pub async? fn find(iter: &mut I, predicate: P) -> Option 44 | where 45 | I: async? Iterator + Sized, 46 | P: FnMut(&T) -> bool; 47 | ``` 48 | 49 | ## generic over all modifier keywords 50 | 51 | 53 | 54 | ```rust 55 | /// A trimmed-down version of the `std::Iterator` trait. 56 | pub trait effect Iterator { 57 | type Item; 58 | effect fn next(&mut self) -> Option; 59 | fn size_hint(&self) -> (usize, Option); 60 | } 61 | ``` 62 | 63 | # Notes 64 | 65 | This is just a postfix version of the originally proposed syntax. 66 | This should appear more familiar, as the question mark is normally used at the end of a 67 | sentence, not at the beginning, and it looks similar to typescripts nullable types. 68 | it also makes generic references more legible `&mut? T` vs `&?mut T`. 69 | -------------------------------------------------------------------------------- /archive/evaluation/overloading-keyword-generics.md: -------------------------------------------------------------------------------- 1 | # Overloading Keyword Generics 2 | 3 | In the previous section we saw that we cannot use `join` to await two futures 4 | concurrently because in "base Rust" we cannot run two closures concurrently. The 5 | capabilities introduced by the superset (async) have no counterpart in the 6 | subset ("base Rust"), and therefore we cannot write it. 7 | 8 | But sometimes we _do_ want to be able to specialize implementations for a 9 | specific context, making use of the capabilities they provide. In order to do 10 | this we need to be able to declare two different code paths, and we propose 11 | _effect overloading_ as the mechanism to do that. 12 | 13 | This problem is not limited to async Rust either; const implementations may want 14 | to swap to platform-specific intrinsics at runtime, but keep using portable 15 | instructions during CTFE. This is only a difference in implementation, and 16 | should not require users to switch between APIs. 17 | 18 | The way we envision _effect overloading_ to work would be similar to 19 | specialization. A base implementation would be declared, with an overload in the 20 | same scope using the same signature except for the effects. The compiler would 21 | pick up on that, and make it work as if the type was written in a polymorphic 22 | fashion. Taking our earlier example, we could imagine the `sum` function could 23 | then be written like this: 24 | 25 | ```rust 26 | // Sum the output of two functions: 27 | default fn sum( 28 | lhs: impl FnMut() -> T, 29 | rhs: impl FnMut() -> T 30 | ) -> T { 31 | lhs() + rhs() 32 | } 33 | 34 | async fn sum( 35 | lhs: impl async FnMut() -> T, 36 | rhs: impl async FnMut() -> T 37 | ) -> T { 38 | let (lhs, rhs) = (lhs(), rhs()).join().await; 39 | lhs + rhs 40 | } 41 | ``` 42 | 43 | We expect _effect overloading_ to not only be useful for performance: we suspect 44 | it may also be required when defining the core (async) IO types in the stdlib 45 | (e.g. `TcpStream`, `File`). These types carry extra fields which their base 46 | counterparts do not. And operations such as reading and writing to them cannot 47 | be written in a polymorphic fashion. 48 | 49 | __While we expect a majority of ecosystem and stdlib code to be written using 50 | _effect polymorphism_, there is a point at which implementations do need to be 51 | specialized, and for that we need _effect overloading_.__ 52 | -------------------------------------------------------------------------------- /archive/evaluation/mir-desugaring.md: -------------------------------------------------------------------------------- 1 | # MIR desugaring 2 | 3 | [Recently](https://rust-lang.zulipchat.com/#narrow/stream/146212-t-compiler.2Fconst-eval/topic/complexity.20of.20constness.20in.20the.20type.20system) 4 | I (Oli) have proposed to add a magic generic parameter on all `const fn foo`, 5 | `impl const Foo for Bar` and `const trait Foo` declarations. This generic 6 | parameter (called `constness` henceforth) is forwarded automatically to all 7 | items used within the body of a const fn. The following code blocks demonstrates 8 | the way I envision this magic generic parameter to be created (TLDR: similar to 9 | desugarings). 10 | 11 | ### Examples 12 | 13 | #### Trait declarations 14 | 15 | ```rust 16 | const trait Foo {} 17 | ``` 18 | 19 | becomes 20 | 21 | ```rust 22 | trait Foo {} 23 | ``` 24 | 25 | #### Generic parameters 26 | 27 | ```rust 28 | const fn foo() {} 29 | ``` 30 | 31 | becomes 32 | 33 | ```rust 34 | fn foo>() {} 35 | ``` 36 | 37 | #### Function bodies 38 | 39 | ```rust 40 | const fn foo() { 41 | bar() 42 | } 43 | ``` 44 | 45 | becomes 46 | 47 | ```rust 48 | fn foo() { 49 | bar::() 50 | } 51 | ``` 52 | 53 | #### Call sites outside of const contexts 54 | 55 | ```rust 56 | fn main() { 57 | some_const_fn(); 58 | } 59 | ``` 60 | 61 | becomes 62 | 63 | ```rust 64 | fn main() { 65 | some_const_fn::(); 66 | } 67 | ``` 68 | 69 | #### Call sites in const contexts 70 | 71 | ```rust 72 | const MOO: () = { 73 | some_const_fn(); 74 | } 75 | ``` 76 | 77 | becomes 78 | 79 | ```rust 80 | const MOO: () = { 81 | some_const_fn::(); 82 | } 83 | ``` 84 | 85 | ### Implementation side: 86 | 87 | We add a fourth kind of generic parameter: `constness`. All `const trait Foo` 88 | implicitly get that parameter. In rustc we remove the `constness` field from 89 | `TraitPredicate` and instead rely on generic parameter substitutions to replace 90 | constness parameters. For now such a generic parameter can either be 91 | `Constness::Required`, `Constness::Not` or `Constness::Param`, where only the 92 | latter is replaced during substitutions, the other two variants are fixed. 93 | Making this work as generic parameter substitution should allow us to re-use all 94 | the existing logic for such substitutions instead of rolling them again. I am 95 | aware of a significant amount of hand-waving happening here, most notably around 96 | where the substitutions are coming from, but I'm hoping we can hash that out in 97 | an explorative implementation 98 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [👋 Welcome](./README.md) 4 | - [✏️ Updates](./updates/README.md) 5 | - [2022-07-27: Announcing the Keyword Generics Initiative](./updates/2022-07-27-announcing-the-keyword-generics-initiative.md) 6 | - [2023-02-23: Keyword Generics Progress Report February 2023](./updates/2023-02-23-keyword-generics-progress-report-feb-2023.md) 7 | - [2024-02-09: Extending Rust's Effect System](./updates/2024-02-09-extending-rusts-effect-system.md) 8 | - [📜 Charter](./CHARTER.md) 9 | - [🔬 Evaluation](./evaluation/README.md) 10 | - [Syntax](./evaluation/syntax/README.md) 11 | - [Template](./evaluation/syntax/_template.md) 12 | - [Attributes](./evaluation/syntax/attributes.md) 13 | - [Const-bool like effects](./evaluation/syntax/const-bool-like-effects.md) 14 | - [Effect as a clause](./evaluation/syntax/effect-as-a-clause.md) 15 | - [Postfix question mark](./evaluation/syntax/postfix-question-mark.md) 16 | - [Where effect bounds](./evaluation/syntax/where-effect-bounds.md) 17 | - [Pattern Types and Backwards Compatibility](./evaluation/pattern-types.md) 18 | - [Auto Concurrency](./evaluation/auto-concurrency.md) 19 | - [Unleakable Types](./evaluation/unleakable-types.md) 20 | - [📚 Draft RFCs](./explainer/README.md) 21 | - [1. Effect-Generic Trait Declarations](./explainer/effect-generic-trait-declarations.md) 22 | - [2. Effect-Generic Bounds and Functions](./explainer/effect-generic-bounds-and-functions.md) 23 | - [3. Effect-Generic Types]() 24 | - [4. Effect Sets and Aliases]() 25 | - [📦 Archive](./archive/README.md) 26 | - [Effects in Rust](./archive/evaluation/effects-in-rust.md) 27 | - [Asynchrony](./archive/evaluation/effects-in-rust/async.md) 28 | - [Compile-Time Execution](./archive/evaluation/effects-in-rust/const.md) 29 | - [Fallibility](./archive/evaluation/effects-in-rust/try.md) 30 | - [Iteration](./archive/evaluation/effects-in-rust/gen.md) 31 | - [Unwinding](./archive/evaluation/effects-in-rust/panic.md) 32 | - [Memory-Safety](./archive/evaluation/effects-in-rust/unsafe.md) 33 | - [Immovability](./archive/evaluation/effects-in-rust/pin.md) 34 | - [Object-Safety](./archive/evaluation/effects-in-rust/sized.md) 35 | - [Ownership](./archive/evaluation/effects-in-rust/ownership.md) 36 | - [Thread-Safety](./archive/evaluation/effects-in-rust/send.md) 37 | - [Effect hierarchy](./archive/evaluation/effect-hierarchy.md) 38 | - [Grouping keyword generics](./archive/evaluation/grouping-keyword-generics.md) 39 | - [Introducing new keyword generics](./archive/evaluation/introducing-new-keyword-generics.md) 40 | - [MIR desugaring](./archive/evaluation/mir-desugaring.md) 41 | - [Overloading](./archive/evaluation/overloading-keyword-generics.md) 42 | - [Prior Art](./archive/evaluation/prior-art.md) 43 | -------------------------------------------------------------------------------- /archive/evaluation/prior-art.md: -------------------------------------------------------------------------------- 1 | # Prior Art 2 | 3 | ## C++: `noexcept(noexcept(…))` 4 | 5 | C++'s `noexcept(noexcept(…))` pattern is used to declare something as `noexcept` 6 | if the evaluated pattern is also `noexcept`. This makes `noexcept` conditional 7 | on the pattern provided. 8 | 9 | This is most commonly used in generic templates to mark the output type as 10 | `noexcept` if all of the input types are `noexcept` as well. 11 | 12 | - [Raymond Chen, “Please Repeat Yourself: The Noexcept(Noexcept(…)) Idiom,” The Old New Thing, April 8, 2022](https://devblogs.microsoft.com/oldnewthing/20220408-00/?p=106438) 13 | 14 | ## C++: implicits and `constexpr` 15 | 16 | `constexpr` can be applied based on a condition. The following example works: 17 | 18 | #### C++ 11 19 | ```cpp 20 | template < 21 | class U = T, 22 | detail::enable_if_t::value> * = nullptr, 23 | detail::enable_forward_value * = nullptr> 24 | constexpr optional(U &&u) : base(in_place, std::forward(u)) {} 25 | 26 | template < 27 | class U = T, 28 | detail::enable_if_t::value> * = nullptr, 29 | detail::enable_forward_value * = nullptr> 30 | constexpr explicit optional(U &&u) : base(in_place, std::forward(u)) {} 31 | ``` 32 | 33 | #### C++ 20 34 | ```cpp 35 | template < 36 | class U = T, 37 | detail::enable_forward_value * = nullptr> 38 | explicit(std::is_convertible::value) constexpr optional(U &&u) : base(in_place, std::forward(u)) {} 39 | ``` 40 | 41 | __todo:__ validate what this does exactly by someone who can actually read C++. 42 | 43 | ## Rust: `maybe-async` crate 44 | 45 | - [fmeow, “maybe-async crate,” January 15, 2020](https://crates.io/crates/maybe-async) 46 | 47 | ## Rust: `fn main` 48 | 49 | Rust provides overloads for `async fn main` through the `Termination` trait. The 50 | `main` function can optionally be made fallible by defining `-> Result<()>` as 51 | the return type. In the ecosystem it's common to extend `fn main` with async 52 | capabilities by annotating it with an attribute. And this mechanism has been 53 | shown to work in the compiler as well by implementing `Termination for F: 54 | Future`. 55 | 56 | The mechanism of overloading for `fn main` differs from what we're proposing, 57 | but the outcomes are functionally the same: greater flexibility in which 58 | function modifiers are accepted, and less need to duplicate / wrap code. 59 | 60 | ## Zig: async functions 61 | 62 | > Zig infers whether a function is async, and allows async/await on non-async 63 | > functions, which means that Zig libraries are agnostic of blocking vs async I/O. 64 | > Zig avoids function colors. 65 | 66 | — [Zig contributors, “Zig In-Depth Overview: Concurrency via Async Functions,” October 1, 2019](https://ziglang.org/learn/overview/#concurrency-via-async-functions) 67 | 68 | ## Swift: async overloading 69 | 70 | ```swift 71 | // Existing synchronous API 72 | func doSomethingElse() { ... } 73 | 74 | // New and enhanced asynchronous API 75 | func doSomethingElse() async { ... } 76 | ``` 77 | 78 | - https://github.com/apple/swift-evolution/pull/1392 79 | -------------------------------------------------------------------------------- /evaluation/syntax/attributes.md: -------------------------------------------------------------------------------- 1 | - Name: `attribute based effects` 2 | - Proposed by: [@oli-obk](https://github.com/oli-obk) 3 | - Original proposal (optional): (url) 4 | 5 | # Design 6 | 7 | Use function and trait attributes to make a function/trait have effect-like behaviour 8 | instead of adding new syntax. There's still some new syntax in trait bounds, but these are 9 | removed by the attribute at attribute expansion time. 10 | 11 | This is experimentally being built with a proc macro in https://github.com/yoshuawuyts/maybe-async-channel. 12 | 13 | ## base (reference) 14 | 15 | 17 | 18 | ```rust 19 | /// A trimmed-down version of the `std::Iterator` trait. 20 | pub trait Iterator { 21 | type Item; 22 | fn next(&mut self) -> Option; 23 | fn size_hint(&self) -> (usize, Option); 24 | } 25 | 26 | /// An adaptation of `Iterator::find` to a free-function 27 | pub fn find(iter: &mut I, predicate: P) -> Option 28 | where 29 | I: Iterator + Sized, 30 | P: FnMut(&T) -> bool; 31 | ``` 32 | 33 | ## always async 34 | 35 | 36 | 37 | ```rust 38 | #[async] 39 | pub trait Iterator { 40 | type Item; 41 | #[async] 42 | fn next(&mut self) -> Option; 43 | fn size_hint(&self) -> (usize, Option); 44 | } 45 | 46 | /// An adaptation of `Iterator::find` to a free-function 47 | #[async] 48 | fn find(iter: &mut I, predicate: P) -> Option 49 | where 50 | I: async Iterator + Sized, 51 | P: async FnMut(&T) -> bool; 52 | ``` 53 | 54 | ## maybe async 55 | 56 | 57 | 58 | ```rust 59 | #[maybe_async] 60 | pub trait Iterator { 61 | type Item; 62 | #[maybe_async] 63 | fn next(&mut self) -> Option; 64 | fn size_hint(&self) -> (usize, Option); 65 | } 66 | 67 | /// An adaptation of `Iterator::find` to a free-function 68 | #[maybe_async] 69 | fn find(iter: &mut I, predicate: P) -> Option 70 | where 71 | I: async Iterator + Sized, 72 | P: async FnMut(&T) -> bool; 73 | ``` 74 | 75 | ## generic over all modifier keywords 76 | 77 | 79 | 80 | ```rust 81 | #[effect] 82 | pub trait Iterator { 83 | type Item; 84 | #[effect] 85 | fn next(&mut self) -> Option; 86 | fn size_hint(&self) -> (usize, Option); 87 | } 88 | 89 | /// An adaptation of `Iterator::find` to a free-function 90 | #[effect] 91 | fn find(iter: &mut I, predicate: P) -> Option 92 | where 93 | I: effect Iterator + Sized, 94 | P: effect FnMut(&T) -> bool; 95 | ``` 96 | 97 | # Notes 98 | 99 | 101 | -------------------------------------------------------------------------------- /archive/evaluation/effects-in-rust/const.md: -------------------------------------------------------------------------------- 1 | # Compile-time Execution (Scoped Effect) 2 | ## Description 3 | 4 | The `const` keyword marks functions as "is allowed to be evaluated during 5 | compilation". When used in scope position its meaning changes slightly to: "this 6 | _will_ be evaluated during compilation". There is no way to declare "must be 7 | evaluated at compilation" functions, causing the meaning of "const" to be 8 | context-dependent. 9 | 10 | | | declaration | usage | 11 | | --------------------------------- | -------------------- | ----------------------------- | 12 | | **keyword never applies** | `fn meow() {}` | `fn hello() { meow() }` | 13 | | **keyword always applies** | - | `const CAT: () = {};` | 14 | | **keyword conditionally applies** | `const fn meow() {}` | `const fn hello() { meow() }` | 15 | 16 | ## Feature Status 17 | 18 | The `const` feature is integrated in a lot of the stdlib and ecosystem already, 19 | but it's notoriously missing any form of const-traits. Because a lot of Rust's 20 | language features make use of traits, this means const contexts have no access 21 | to iteration, `Drop` handlers, closures, and more. 22 | 23 | ## Feature categorization 24 | 25 | | Position | Syntax | 26 | | ----------- | ------------------------------ | 27 | | Effect | `const fn` | 28 | | Yield | N/A | 29 | | Apply | automatic | 30 | | Consume | `const {}`, `const X: Ty = {}` | 31 | | Reification | N/A | 32 | 33 | ## Positions Available 34 | 35 | | Position | Available | Example | 36 | | ------------------- | --------- | ---------------------------------- | 37 | | Manual trait impl | ❌ | N/A | 38 | | Free functions | ✅ | `const fn meow() {}` | 39 | | Inherent functions | ✅ | `impl Cat { const fn meow() {} } ` | 40 | | Trait methods | ⏳ | `trait Cat { const fn meow() {} }` | 41 | | Trait declarations | ❌ | `const trait Cat {}` | 42 | | Block scope | ✅ | `fn meow() { const {} }` | 43 | | Argument qualifiers | ❌ | `fn meow(cat: impl const Cat) {}` | 44 | | Data types | ❌ | `const struct Cat {}` | 45 | | Drop | ❌ | `impl const Drop for Cat {}` | 46 | | Closures | ❌ | `const ǀǀ {}` | 47 | | Iterators | ❌ | `for cat in cats {}` | 48 | 49 | ## Refinements 50 | 51 | There are currently no refiments to the compile-time execution effect. 52 | 53 | ## Interactions with other effects 54 | 55 | ### Asynchrony 56 | ### Compile-time Execution 57 | ### Fallibility 58 | ### Iteration 59 | ### Unwinding 60 | ### Memory-Safety 61 | ### Immovability 62 | ### Object-Safety 63 | ### Ownership 64 | ### Thread-Safety 65 | 66 | ## References 67 | 68 | - [Keywords II: Const Syntax](https://blog.yoshuawuyts.com/const-syntax/) 69 | - [Const as an auto-trait](https://without.boats/blog/const-as-an-auto-trait/) 70 | -------------------------------------------------------------------------------- /archive/evaluation/keyword-conditions.md: -------------------------------------------------------------------------------- 1 | # Conditions for keywords 2 | 3 | When comparing `const fn` and `async fn` we can observe that keywords may find 4 | themselves in three different states. Let's start with `async fn`, we can 5 | observe that there are two states in which it can find itself using today's 6 | syntax: 7 | 8 | ```rust 9 | // Always non-async 10 | fn copy(reader: &mut R, writer: &mut W) -> io::Result 11 | where 12 | R: Read + ?Sized, 13 | W: Write + ?Sized, 14 | {} 15 | 16 | // Always async 17 | async fn copy(reader: &mut R, writer: &mut W) -> io::Result 18 | where 19 | R: AsyncRead + ?Sized, 20 | W: AsyncWrite + ?Sized, 21 | {} 22 | ``` 23 | 24 | Assuming we'd want to enable `async` modifiers on traits, we might instead be 25 | able to the async variant as: 26 | 27 | ```rust 28 | // Always async 29 | async fn copy(reader: &mut R, writer: &mut W) -> io::Result 30 | where 31 | R: async Read + ?Sized, 32 | W: async Write + ?Sized, 33 | {} 34 | ``` 35 | 36 | For `const` there are two states as well, but they're different from the states 37 | of `async`: 38 | 39 | ```rust 40 | // Always non-const 41 | fn add(a: T, b: T>) -> T::Output { 42 | a + b 43 | } 44 | 45 | // This will be `const` if called in const contexts, non-const if 46 | // called in non-const contexts. 47 | const fn add(a: T, b: T>) -> T::Output { 48 | a + b 49 | } 50 | ``` 51 | 52 | These states are different from async Rust. We can plot them as such: 53 | 54 | | | keyword `async` | keyword `const` | 55 | | --------------------------------- | -------------------- | --------------------- | 56 | | **keyword never applies** | `fn foo() {}` | `fn foo() {}` | 57 | | **keyword always applies** | `async fn foo() {}` | `const FOO: () = {};` | 58 | | **keyword conditionally applies** | `~async fn foo() {}` | `const fn foo() {}` | 59 | 60 | For the `async` keyword, we're exploring whether we can make it apply 61 | conditionally as well. Because it's a superset of Rust, with wide ranging 62 | implications, just making it "async depending on the context you call it in", 63 | similar to `const` could have surprising behaviour. 64 | 65 | Going back to the previous example, but this time allowing the same function to 66 | be called asynchronously and synchronously: 67 | 68 | ```rust 69 | async fn copy(reader: &mut R, writer: &mut W) -> io::Result 70 | where 71 | R: ~async Read + ?Sized, 72 | W: ~async Write + ?Sized, 73 | { 74 | } 75 | ``` 76 | 77 | Now, if we just allowed non-async readers and writers, we could run into 78 | trouble. Consider the following function body: 79 | 80 | ```rust 81 | let x = reader.read_line().await; 82 | let z = writer.write(x); 83 | let y = reader.read_line().await; 84 | z.await 85 | ``` 86 | 87 | if the reader and writer were in fact synchronous and this were implemented 88 | naively, this could already block at the write call instead of the await. 89 | Instead what actually happens is that `z` is a "fake future", so basically just 90 | an argumentless `FnOnce` closure of the actual logic and `z.await` executes that 91 | closure. 92 | 93 | The chance of this occurring would would be highest specifically for types which 94 | were implemented _incorrectly_: in order for async polymorphism to work, the 95 | behavior between async and non-async code paths must be identical. For async 96 | this means that side-effects should not start _until `.await` has been called._ 97 | This should be not expressable for any code written using async polymorphism. 98 | And should be considered a bug for code written using _async overloading_ (more 99 | on that in a next section) since behavior should match. 100 | 101 | Given we're seeking to provide a complete stdlib experience for _all_ variants 102 | of Rust, we have a fair amount of control over this experience through the 103 | stdlib, and in addition to guidance we can provide assurances which will limit 104 | the practicality of this occuring in the wild. 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # keyword generics initiative 2 | 32 | 33 | ![initiative status: active](https://img.shields.io/badge/status-active-brightgreen.svg) 34 | 35 | ## What is this? 36 | 37 | This page tracks the work of the keyword generics [initiative]! To learn more 38 | about what we are trying to do, and to find out the people who are doing it, 39 | take a look at the [charter]. 40 | 41 | [charter]: ./CHARTER.md 42 | [initiative]: https://lang-team.rust-lang.org/initiatives.html 43 | 44 | ## Current status 45 | 46 | The following table lists of the stages of an initiative, along with links to the artifacts that will be produced during that stage. 47 | 48 | | Stage | State | Artifact(s) | 49 | | ------------------ | ----- | ------------------------------------------------------------------- | 50 | | [Proposal] | ✅ | [Proposal issue](https://github.com/rust-lang/lang-team/issues/162) | 51 | | | ✅ | [Charter](./CHARTER.md) | 52 | | [Experimental] | ✅ | [Evaluation](./evaluation/) | 53 | | [Development] | 🦀 | [Draft RFCs](./explainer/) | 54 | | | 🦀 | [Tracking issue](https://github.com/rust-lang/rust/) | 55 | | [Feature complete] | 💤 | Stabilization report | 56 | | [Stabilized] | 💤 | | 57 | 58 | [Proposal]: https://lang-team.rust-lang.org/initiatives/process/stages/proposal.html 59 | [Experimental]: https://lang-team.rust-lang.org/initiatives/process/stages/proposal.html 60 | [Development]: https://lang-team.rust-lang.org/initiatives/process/stages/development.html 61 | [Feature complete]: https://lang-team.rust-lang.org/initiatives/process/stages/feature-complete.html 62 | [Stabilized]: https://lang-team.rust-lang.org/initiatives/process/stages/stabilized.html 63 | 64 | Key: 65 | 66 | * ✅ -- phase complete 67 | * 🦀 -- phase in progress 68 | * 💤 -- phase not started yet 69 | 70 | ## How Can I Get Involved? 71 | 72 | * Check for 'help wanted' issues on this repository! 73 | * If you would like to help with development, please contact the [owner](./charter.md#membership) to find out if there are things that need doing. 74 | * If you would like to help with the design, check the list of active [design questions](./design-questions/README.md) first. 75 | * If you have questions about the design, you can file an issue, but be sure to check the [FAQ](./FAQ.md) or the [design-questions](./design-questions/README.md) first to see if there is already something that covers your topic. 76 | * If you are using the feature and would like to provide feedback about your experiences, please [open a "experience report" issue]. 77 | * If you are using the feature and would like to report a bug, please open a regular issue. 78 | 79 | We also participate on [Zulip][chat-link], feel free to introduce yourself over there and ask us any questions you have. 80 | 81 | [open issues]: /issues 82 | [chat-link]: https://rust-lang.zulipchat.com/#narrow/stream/328082-t-lang.2Fkeyword-generics 83 | [team-toml]: https://github.com/rust-lang/team/blob/master/teams/initiative-keyword-generics-initiative.toml 84 | 85 | ## Building Documentation 86 | This repository is also an mdbook project. You can view and build it using the 87 | following command. 88 | 89 | ``` 90 | mdbook serve 91 | ``` 92 | -------------------------------------------------------------------------------- /archive/evaluation/grouping-keyword-generics.md: -------------------------------------------------------------------------------- 1 | # Grouping Keyword Generics 2 | 3 | We expect it to be common that if a function takes generics and has conditional 4 | keywords on those, it will want to be conditional over *all* keywords on those 5 | generics. So in order to not have people repeat params over and over, we should 6 | provide shorthand syntax. 7 | 8 | Here is the "base" variant we're changing: 9 | ```rust 10 | // without any effects 11 | fn find( 12 | iter: impl Iterator, 13 | closure: impl FnMut(&I) -> bool, 14 | ) -> Option { 15 | ... 16 | } 17 | ``` 18 | 19 | We could imagine wanting a fallible variant of this which can short-circuit 20 | based on whether an `Error` is returned or not. We could imagine the "base" 21 | version using a `TryTrait` notation, and the "effect" version using the `throws` 22 | keyword. Both variants would look something like this: 23 | 24 | ```rust 25 | // fallible without effect notation 26 | fn try_find( 27 | iter: impl TryIterator, 28 | closure: impl TryFnMut<(&I), E> -> bool, 29 | ) -> Result, E> { 30 | ... 31 | } 32 | 33 | // fallible with effect notation 34 | fn try_find( 35 | iter: impl Iterator ~yeets E, 36 | closure: impl FnMut(&I) ~yeets E -> bool, 37 | ) -> Option ~yeets E { 38 | ... 39 | } 40 | ``` 41 | 42 | For `async` we could do something similar. The "base" version would use 43 | `AsyncTrait` variants. And the "effect" variant would use the `async` keyword: 44 | 45 | ```rust 46 | // async without effect notation 47 | fn async_find( 48 | iter: impl AsyncIterator, 49 | closure: impl AsyncFnMut(&I) -> bool, 50 | ) -> impl Future> { 51 | ... 52 | } 53 | 54 | // async with effect notation 55 | ~async fn async_find( 56 | iter: impl ~async Iterator, 57 | closure: impl ~async FnMut(&I) -> bool, 58 | ) -> Option { 59 | ... 60 | } 61 | ``` 62 | 63 | Both the "fallible" and "async" variants mirror each other closely. And it's 64 | easy to imagine we'd want to be conditional over both. However, if neither the 65 | "base" or the "effect" variants are particularly pleasant. 66 | 67 | ```rust 68 | // async + fallible without effect notation 69 | fn try_async_find( 70 | iter: impl TryAsyncIterator>, 71 | closure: impl TryAsyncFnMut<(&I), E> -> bool, 72 | ) -> impl Future>> { 73 | ... 74 | } 75 | 76 | // async + fallible with effect notation 77 | ~async fn try async_find( 78 | iter: impl ~async Iterator ~yeets E, 79 | closure: impl ~async FnMut(&I) ~yeets E -> bool, 80 | ) -> Option ~yeets E { 81 | ... 82 | } 83 | ``` 84 | 85 | The "base" variant is entirely unworkable since it introduces a combinatorial 86 | explosion of effects ("fallible" and "async" are only two examples of effects). 87 | The "effect" variant is a little better because it composes, but even with just 88 | two effects it looks utterly overwhelming. Can you imagine what it would look 89 | like with three or four? Yikes. 90 | 91 | So what if we could instead treat effects as an actual generic parameter? As we 92 | discussed earlier, in order to lower effects we already need a new type of 93 | generic at the MIR layer. But what if we exposed that type of generics as user 94 | syntax too? We could imagine it to look something like this: 95 | 96 | ```rust 97 | // conditional 98 | fn any_find( 99 | iter: impl F * Iterator, 100 | closure: impl F * FnMut(&I) -> bool, 101 | ) -> F * Option { 102 | ... 103 | } 104 | ``` 105 | 106 | There are legitimate questions here though. Effects which provide a superset of 107 | base Rust may change the way we write Rust. The clearest example of this is 108 | `async`: would having an `effect F` require that we when we invoke our `closure` 109 | that we suffix it with `.await`? What about a `try` effect, would that require 110 | that we suffix it with a `?` operator? The effects passed to the function might 111 | need to change the way we author the function body [^implication]. 112 | 113 | [^implication]: One interesting thing to keep in mind is that the total set of 114 | effects is strictly _bounded_. None of these mechanisms would be exposed to 115 | end-users to define their own effects, but only used by the Rust language. This 116 | means we can know which effects are part of the set. And any change in calling 117 | signature (e.g. adding `.await` or `?`, etc.) can be part of a Rust edition. 118 | 119 | Another question is about _bounds_. Declaring an `effect F` is maximally 120 | inclusive: it would capture all effects. Should we be able to place restrictions 121 | on this effect, and if so what should that look like? 122 | -------------------------------------------------------------------------------- /CHARTER.md: -------------------------------------------------------------------------------- 1 | # 📜 keyword generics Charter 2 | 3 | One of Rust's defining features is the ability to write functions which are 4 | _generic_ over their input types. That allows us to write a function once, 5 | leaving it up to the compiler to generate the right implementations for us. 6 | 7 | When we introduce a new keyword for something which used to be a trait, we not 8 | only gain new functionality - we also lose the ability to be generic over that 9 | keyword. This proposal seeks to change that by introducing keyword generics: the 10 | ability to be generic over specific keywords. 11 | 12 | This proposal is scoped to the `const` and `async` keywords only, but is designed 13 | to be leveraged by other keywords as well in the future. Keywords are valuable, 14 | generics are valuable, users of Rust shouldn't have to choose between the two. 15 | 16 | 20 | 21 | ## Proposal 22 | 23 | We're in the process of adding new features to Rust. The Const WG is creating an 24 | extension to Rust which enables arbitrary computation at compile time. 25 | While the Async WG is in the process of adding capabilities for asynchronous 26 | computation. We've noticed that both these efforts have a lot in common, and may 27 | in fact require similar solutions. This document describes a framework for 28 | thinking about these language features, describes their individual needs, and 29 | makes the case that we should be considering a generalized language design for 30 | "keywords" (aka "definitely not effects"), so that we can ensure that the Rust 31 | language and standard library remain consistent in the face of extensions. 32 | 33 | ## A broad perspective on language extensions 34 | 35 | `const fn` and `async fn` are similar language extensions, but the way they 36 | extend the language is different: 37 | 38 | - `const fn` creates a *subset* of "base Rust", enabling functions to be 39 | executed during compilation. `const` functions can be executed in "base" 40 | contexts, while the other way around isn't possible. 41 | - `async fn` creates a *superset* of "base Rust", enabling functions to be 42 | executed asynchronously. `async` types cannot be executed in "base" contexts 43 | [^bridge], but "base" in `async` contexts _is_ possible. 44 | 45 | [^bridge]: In order to bridge async and non-async Rust, functionality such as 46 | `thread::block_on` or `async fn` must be used, which runs a future to completion 47 | from a synchronous context. `const` Rust does not require such a bridge, since 48 | the difference in contexts is "compile time" and "run-time". 49 | 50 | ``` 51 | +---------------------------+ 52 | | +-----------------------+ | Compute values: 53 | | | +-------------------+ | | - types 54 | | | | | | | - numbers 55 | | | | const Rust |-------{ - functions 56 | | | | | | | - control flow 57 | Access to the host: | | +-------------------+ | | - traits (planned) 58 | - networking | | | | - containers (planned) 59 | - filesystem }--------| "base" Rust | | 60 | - threads | | | | 61 | - system time | +-----------------------+ | 62 | | | Control over execution: 63 | | async Rust |---{ - ad-hoc concurrency 64 | | | - ad-hoc cancellation 65 | +---------------------------+ - ad-hoc pausing/resumption 66 | 67 | ``` 68 | 69 | In terms of standard library these relationships also mirror each other. "Base" 70 | Rust will want to do everything during runtime what `const` rust can do, but in 71 | addition to that also things like network and filesystem IO. Async Rust will in 72 | turn want to do everything "base" Rust can do, but in addition to that will also 73 | want to introduce methods for ad-hoc concurrency, cancellation, and execution 74 | control. It will also want to do things which are blocking in "base" Rust as 75 | non-blocking in async Rust. 76 | 77 | And it doesn't stop with `const` and `async` Rust; it's not hard to imagine that 78 | other annotations for "can this panic", "can this return an error", "can this 79 | yield values" may want to exist as well. All of which would present extensions 80 | to the "base" Rust language, which would need to be introduced in a way which 81 | keeps it feeling like a single language - instead of several disjoint languages 82 | in a trenchcoat. 83 | 84 | ## Membership 85 | 86 | | Role | Github | 87 | |-----------|-----------------------------------| 88 | | [Owner] | [Yosh Wuyts](https://github.com/yoshuawuyts) | 89 | | [Owner] | [Oli Scherer](https://github.com/oli-obk) | 90 | | [Liaison] | [Niko Matsakis?](https://github.com/nikomatsakis) | 91 | 92 | [Owner]: https://lang-team.rust-lang.org/initiatives/process/roles/owner.html 93 | [Liaison]: https://lang-team.rust-lang.org/initiatives/process/roles/liaison.html 94 | -------------------------------------------------------------------------------- /evaluation/syntax/effect-as-a-clause.md: -------------------------------------------------------------------------------- 1 | - Name: `effect-as-a-clause` 2 | - Proposed by: [@mominul](https://github.com/mominul) [@satvikpendem](https://github.com/satvikpendem) 3 | - Original proposal (optional): (https://github.com/rust-lang/keyword-generics-initiative/issues/14) 4 | 5 | # Design 6 | We want to propose the usage of the `effect` clause to achieve operation genericity, for example: 7 | ```rust 8 | trait Read { 9 | fn read(&mut self, buf: &mut [u8]) -> Result 10 | effect 11 | async; 12 | 13 | fn read_to_string(&mut self, buf: &mut String) -> Result 14 | effect 15 | async 16 | { .. } 17 | } 18 | 19 | /// Function to read from the file into a string which may exhibit async or const effect 20 | fn read_to_string(path: &str) -> io::Result 21 | effect 22 | async, const 23 | { 24 | let mut string = String::new(); 25 | 26 | // We can be conditional over the context the function has been called from, 27 | // only when the function declaration has the `effect` clause 28 | if async || !async { 29 | let mut file = File::open("foo.txt")?; // File implements Read 30 | // Because `read_to_string` is also an `effect` function that may or may not exhibit 31 | // async-ness par the declaration, we can use it on both contexts (async/sync) 32 | // we are placing the condition on. 33 | file.read_to_string(&mut string)?; // .await will be inferred. 34 | } else { // must be const 35 | // As the `read_to_string` doesn't exhibit const-ness, we'll need to handle it ourselves. 36 | string = include_str!(path).to_string(); 37 | } 38 | 39 | Ok(string) 40 | } 41 | 42 | /// A normal function 43 | fn read() { 44 | let data = read_to_string("hello.txt").unwrap(); 45 | } 46 | 47 | /// A async function 48 | async fn read() { 49 | let data = read_to_string("hello.txt").await.unwrap(); 50 | } 51 | 52 | /// A const function 53 | const fn read() { 54 | let data = read_to_string("hello.txt").unwrap(); 55 | } 56 | ``` 57 | So in a nutshell, a function declaration with an `effect` clause is a special type of function that **may** or **may not** exhibit async or const behavior(effect) and it **depends on the context of the function being called from** and we can **execute a different piece of code according to the context** from the function was called from too (like the `const_eval_select`, resolves [#6](https://github.com/rust-lang/keyword-generics-initiative/issues/6)): 58 | 59 | ```rust 60 | fn function() -> Result<()> 61 | effect 62 | async, const 63 | { 64 | // ... 65 | if async { 66 | // code for handling stuff asynchronously 67 | } else if const { 68 | // code for handling stuff `const`-way 69 | else { 70 | // code for handling stuff synchronously 71 | } 72 | // ... 73 | } 74 | ``` 75 | ## base (reference) 76 | 77 | 79 | 80 | ```rust 81 | /// A trimmed-down version of the `std::Iterator` trait. 82 | pub trait Iterator { 83 | type Item; 84 | fn next(&mut self) -> Option; 85 | fn size_hint(&self) -> (usize, Option); 86 | } 87 | 88 | /// An adaptation of `Iterator::find` to a free-function 89 | pub fn find(iter: &mut I, predicate: P) -> Option 90 | where 91 | I: Iterator + Sized, 92 | P: FnMut(&T) -> bool; 93 | ``` 94 | 95 | ## always async 96 | 97 | 98 | 99 | ```rust 100 | pub trait Iterator { 101 | type Item; 102 | async fn next(&mut self) -> Option; 103 | fn size_hint(&self) -> (usize, Option); 104 | } 105 | 106 | pub async fn find(iter: &mut I, predicate: P) -> Option 107 | where 108 | I: Iterator + Sized, 109 | P: async FnMut(&T) -> bool; 110 | ``` 111 | 112 | ## maybe async 113 | 114 | 115 | 116 | ```rust 117 | pub trait Iterator { 118 | type Item; 119 | fn next(&mut self) -> Option 120 | effect async; 121 | fn size_hint(&self) -> (usize, Option); 122 | } 123 | 124 | pub fn find(iter: &mut I, predicate: P) -> Option 125 | where 126 | I: Iterator + Sized, 127 | P: FnMut(&T) -> bool effect async; 128 | effect 129 | async 130 | ``` 131 | 132 | ## generic over all modifier keywords 133 | 134 | 136 | 137 | ```rust 138 | pub trait Iterator { 139 | type Item; 140 | fn next(&mut self) -> Option 141 | effect async, const; 142 | fn size_hint(&self) -> (usize, Option); 143 | } 144 | 145 | pub fn find(iter: &mut I, predicate: P) -> Option 146 | where 147 | I: Iterator + Sized, 148 | P: FnMut(&T) -> bool effect async, const; 149 | effect 150 | async, const 151 | ``` 152 | 153 | # Notes 154 | 155 | 157 | We can introduce `maybe` keyword instead of `effect` if it seems more appropriate terminology for the semantics described in this proposal. -------------------------------------------------------------------------------- /archive/evaluation/what-to-support.md: -------------------------------------------------------------------------------- 1 | # What should we support 2 | 3 | There are a lot of things we would ideally support, this file is intended as a collection of all of them. Not all of them will end up being supported as they may conflict with other design and implementation goals. 4 | 5 | ## purely const 6 | 7 | Dealing with `const`-ness does not change type signatures. 8 | 9 | ```rust 10 | impl [T; N] { 11 | // `map` is const if `F` is const. 12 | // 13 | // This is the case as `map` eagerly uses `f` in 14 | // its function body. 15 | const fn map>(self, f: F) -> [U; N] 16 | where 17 | F: const FnMut(T) -> U, 18 | { 19 | // ... 20 | } 21 | } 22 | 23 | trait Iterator { 24 | type Item; 25 | // `map` is always `const`. 26 | // 27 | // This is the case as `map` only creates the `Map` 28 | // struct but does not eagerly use `f`. 29 | const fn map(self, f: F) -> Map 30 | where 31 | Self: Sized, 32 | F: FnMut(Self::Item) -> B, 33 | { 34 | Map::new(self, f) 35 | } 36 | } 37 | 38 | pub struct Map { 39 | iter: I, 40 | f: F, 41 | } 42 | 43 | impl> const Iterator for Map 44 | where 45 | I: const Iterator, 46 | F: const FnMut(I::Item) -> B, 47 | { 48 | type Item = B; 49 | // ... 50 | } 51 | ``` 52 | 53 | ## how does `impl const Trait` work 54 | 55 | `const` trait impls are intended to mean that **all** functions of that trait are const. But, how const? 56 | 57 | As seen in the `Iterator::map` example up above, simply adding `const` to all trait bounds of trait methods is too restrictive. The same holds for a lot of other iterator adaptors. 58 | 59 | A similar issue is dropping generic parameters. Looking at the following example: 60 | ```rust 61 | trait Trait { 62 | fn takes_self(self); 63 | 64 | fn doesnt_take_self(); 65 | 66 | fn has_other_param(); 67 | 68 | fn uses_other_param(x: T); 69 | } 70 | ``` 71 | 72 | What would a `const` impl of that trait look like? The most permissive option for implementors would be: 73 | ```rust 74 | impl> const Trait for T { 75 | fn takes_self(self) 76 | where 77 | T: const Destruct, 78 | {} 79 | 80 | fn doesnt_take_self() 81 | where 82 | T: const Destruct, 83 | {} 84 | 85 | fn has_other_param() 86 | where 87 | T: const Destruct, 88 | U: const Destruct, 89 | {} 90 | 91 | fn uses_other_param(x: U) 92 | where 93 | T: const Destruct, 94 | U: const Destruct, 95 | {} 96 | } 97 | ``` 98 | While the impl which can be used in the most contexts would have no `const Destruct` bounds at all. Any automated way of figuring this out would sometimes be incorrect, as functions with equal signatures can still have different requirements. 99 | 100 | Considering that traits apparently have to opt-in to being `const`-able, due to backwards compatability of default methods, we could require the trait itself to declare its bounds when const, which may look like the following: 101 | ```rust 102 | const trait Trait { 103 | // Sidenote: the `X: const Destruct` for generic parameters 104 | // of the trait could instead be moved to the super traits or 105 | // the impl instead. This is probably the more sensible approach 106 | // for most traits. 107 | fn takes_self(self) 108 | where 109 | Self: const Destruct; 110 | 111 | // No bounds. 112 | fn doesnt_take_self(); 113 | 114 | // Not visible in the method signature, 115 | // but maybe its expected that this function creates 116 | // and drops an instance of the `Self` type, so the 117 | // trait author assumes that `Self` can be dropped. 118 | fn has_other_param(); 119 | where 120 | Self: const Destruct; 121 | 122 | fn uses_other_param(x: T) 123 | where 124 | T: const Destruct; 125 | } 126 | ``` 127 | 128 | ## multi `const` effect fun 129 | 130 | ```rust 131 | const fn merge, const>( 132 | x: Vec, 133 | y: Vec, 134 | f1: F1, 135 | f2: F2 136 | ) -> Vec 137 | where 138 | F1: const FnMut(T) -> R, 139 | F2: const FnMut(U) -> R, 140 | { 141 | // ... 142 | } 143 | ``` 144 | This example has a function with two arguments which should only be `const` if **both** arguments are `const`. More importantly, it should be possible to have 1 argument be `const` while the other one is not without causing an error. 145 | 146 | This means that using the same `const` effect parameter (in our impl) doesn't work. Checking `F1: const FnMut(T) -> R` may infer the `const`-ness to `yes` at which point `F2: const FnMut(U) -> R` would result in an error if `F2` isn't const, even though it should only result in `merge` not being `const`. 147 | 148 | Unifying const effect infer variables may require some experimentation but shouldn't be too bad. 149 | 150 | ## `async` and more involved effects 151 | 152 | it's confusing that `const` is actually the reverse of an effect as it reduces what this function may do. `async` functions are actually **more powerful** than ordinary functions. 153 | 154 | **TODO: integrate the rest of https://hackmd.io/Kit1pLhmTVKcvjQHYq29Xw** -------------------------------------------------------------------------------- /archive/evaluation/effects-in-rust/async.md: -------------------------------------------------------------------------------- 1 | # Asynchrony (Scoped Effect) 2 | ## Description 3 | 4 | Asynchrony in Rust enables non-blocking operations to be authored in an 5 | imperative fashion. This can be helpful for performance reasons, but 6 | feature-wise it enables "arbitrary concurrency" and "arbitrary cancellation" of 7 | computations. These can in turn be composed and leveraged by higher-level 8 | control-flow primitives such as "arbitrary timeouts" and "arbitrary 9 | parallel execution". 10 | 11 | Asynchrony in Rust is implemented using a pair of keywords. `async` is used to 12 | create an async context which is reified into a state machine backed by the 13 | `Future` trait. And `.await` is used on the call-site to access the values 14 | inside of an async context. Because `.await` can only be called inside of 15 | `async` contexts, it eventually needs to be consumed by a top-level function 16 | which knows how to run a future to completion. 17 | 18 | ## Feature Status 19 | 20 | `async/.await` in Rust is considered "MVP stable". This means the reification of 21 | the effect is stable, and both the `async` and `.await` keywords exist in the 22 | language, but not all keyword positions are available yet. 23 | 24 | ## Feature categorization 25 | 26 | | Position | Syntax | 27 | | ----------- | --------------------------------------- | 28 | | Effect | `async` | 29 | | Yield | N/A | 30 | | Apply | `.await` | 31 | | Consume | `thread::block_on` †, `async fn main` ‡ | 32 | | Reification | `impl Future` | 33 | 34 | > † `thread::block_on` is not yet part of the stdlib, and only exists as a 35 | > library feature. An example implementation can be found in the 36 | > [`Wake`](https://doc.rust-lang.org/std/task/trait.Wake.html#examples) docs. 37 | 38 | > ‡ `async fn main` is not yet part of the language, and only exists as a 39 | > proc-macro extension as part of the ecosystem. It chiefly wraps the existing `fn 40 | > main` logic in a `thread::block_on` call. 41 | 42 | ## Positions Available 43 | 44 | | Position | Available | Example | 45 | | ------------------- | --------- | ---------------------------------- | 46 | | Manual trait impl | ✅ | `impl Future for Cat {}` | 47 | | Free functions | ✅ | `async fn meow() {}` | 48 | | Inherent functions | ✅ | `impl Cat { async fn meow() {} } ` | 49 | | Trait methods | ⏳ | `trait Cat { async fn meow() {} }` | 50 | | Trait declarations | ❌ | `async trait Cat {}` | 51 | | Block scope | ✅ | `fn meow() { async {} }` | 52 | | Argument qualifiers | ❌ | `fn meow(cat: impl async Cat) {}` | 53 | | Data types † | ❌ | `async struct Cat {}` | 54 | | Drop † | ❌ | `impl async Drop for Cat {}` | 55 | | Closures | ❌ | `async ǀǀ {}` | 56 | | Iterators | ❌ | `for await cat in cats {}` | 57 | 58 | > † In non-async Rust if you place a value which implements `Drop` inside of 59 | > another type, the destructor of that value is run when the enclosing type is 60 | > destructed. This is called _drop-forwarding_. In order for drop-forwarding to 61 | > work with async drop, some form of "async value" notation will be required. 62 | 63 | ## Refinements 64 | 65 | | Modifier | Description | 66 | | ----------------- | ------------------------------------ | 67 | | cancellation-safe | Has no associated future-local state | 68 | 69 | ### Cancellation-Safe Futures 70 | 71 | "cancellation-safety" is currently more like a term of art than an first-class 72 | term. It is a property used and relied upon by ecosystem APIs, but it is not 73 | represented in the type system anywhere. Which means APIs which rely on 74 | "cancellation-safety" do so without compiler-backing, which makes them a 75 | notorious source of bugs. This should probably be fixed, and when we do we 76 | probably will not want to call it "cancellation-safety" since it relates less to 77 | "cancellation" and more to the statefulness of futures, and whether or not they 78 | can be recreated without side-effects or data loss. 79 | 80 | ### Fused Futures 81 | 82 | A `FusedFuture` super-trait also exists, but it does not meaningfully feel like 83 | a modifier of the "async" effect. It only adds an `is_terminated` method which 84 | returns a bool. It does not inherently change the semantic functioning of the 85 | underlying `Iterator` trait, or enhance it with behavior which is otherwise 86 | absent. This is different from e.g. `FusedIterator` which says something about 87 | the behavior of the `Iterator::next` function. 88 | 89 | It's also worth noting that the `FusedFuture` trait is mostly useful for the 90 | `select!` control-flow construct. Without that, `FusedFuture` would likely not 91 | see much use 92 | ([ref](https://blog.yoshuawuyts.com/futures-concurrency-3/#fuse-requirements)). 93 | 94 | ## Interactions with other effects 95 | ### Asynchrony 96 | ### Compile-time Execution 97 | ### Fallibility 98 | ### Iteration 99 | ### Unwinding 100 | ### Memory-Safety 101 | ### Immovability 102 | ### Object-Safety 103 | ### Ownership 104 | ### Thread-Safety 105 | -------------------------------------------------------------------------------- /evaluation/syntax/where-effect-bounds.md: -------------------------------------------------------------------------------- 1 | - Name: `where effect bounds` 2 | - Proposed by: [@CaioOliveira793](https://github.com/CaioOliveira793) 3 | - Original proposal: None 4 | 5 | # Design 6 | 7 | This syntax focus on being simple and recognizable rust code, with the possibility to incrementally extend the capabilities that keyewords generic may provide. 8 | 9 | ## base (reference) 10 | 11 | 13 | 14 | ```rust 15 | /// A trimmed-down version of the `std::Iterator` trait. 16 | pub trait Iterator { 17 | type Item; 18 | fn next(&mut self) -> Option; 19 | fn size_hint(&self) -> (usize, Option); 20 | } 21 | 22 | /// An adaptation of `Iterator::find` to a free-function 23 | pub fn find(iter: &mut I, predicate: P) -> Option 24 | where 25 | I: Iterator + Sized, 26 | P: FnMut(&T) -> bool; 27 | ``` 28 | 29 | ## always async 30 | 31 | 32 | 33 | ```rust 34 | pub trait Iterator 35 | where 36 | effect: async 37 | { 38 | type Item; 39 | 40 | // opt-in for a always async effect 41 | fn next(&mut self) -> Option 42 | where 43 | effect: async; 44 | 45 | // the size_hint is left unchanged, since the effect is opt-in 46 | fn size_hint(&self) -> (usize, Option); 47 | } 48 | 49 | pub fn find(iter: &mut I, predicate: P) -> Option 50 | where 51 | effect: async, 52 | I: Iterator + Sized, 53 | ::effect: async, 54 | P: FnMut(&T) -> bool, 55 |

::effect: async; 56 | ``` 57 | 58 | ## maybe async 59 | 60 | 61 | 62 | ```rust 63 | pub trait Iterator 64 | where 65 | effect: ?async 66 | { 67 | type Item; 68 | 69 | fn next(&mut self) -> Option 70 | where 71 | effect: ?async; 72 | 73 | fn size_hint(&self) -> (usize, Option); 74 | } 75 | 76 | pub fn find(iter: &mut I, predicate: P) -> Option 77 | where 78 | effect: ?async, 79 | I: Iterator + Sized, 80 | ::effect: ?async, 81 | P: FnMut(&T) -> bool, 82 |

::effect: ?async; 83 | ``` 84 | 85 | ## generic over all modifier keywords 86 | 87 | 89 | 90 | ```rust 91 | pub trait Iterator 92 | where 93 | // LIMITATION: in order to be generic over all keywords the effect clause must specify all keywords available 94 | effect: ?async + ?const 95 | { 96 | type Item; 97 | 98 | fn next(&mut self) -> Option 99 | where 100 | effect: ?async + ?const; 101 | 102 | fn size_hint(&self) -> (usize, Option); 103 | } 104 | 105 | pub fn find(iter: &mut I, predicate: P) -> Option 106 | where 107 | effect: ?async + ?const, 108 | I: Iterator + Sized, 109 | ::effect: ?async + ?const, 110 | P: FnMut(&T) -> bool, 111 |

::effect: ?async + ?const; 112 | ``` 113 | 114 | # Notes 115 | 116 | ## Trait effect bounds 117 | 118 | The syntax for specifying the effect of a trait implemented by some generic argument `::effect: const` could be different 119 | 120 | ```rust 121 | I: Iterator 122 | 123 | // or 124 | 125 | // Associated type bounds [RFC 2289](https://github.com/rust-lang/rfcs/blob/master/text/2289-associated-type-bounds.md) 126 | I: Iterator 127 | ``` 128 | 129 | The current way mimics how associated types are bound 130 | 131 | ```rust 132 | fn print_iter(iter: I) 133 | where 134 | effect: ?async, 135 | I: Iterator, 136 | ::Item: Display, 137 | ::effect: ?async; 138 | ``` 139 | 140 | ## Explicit generic over all modifier keywords 141 | 142 | The syntax does not give shortans for specifying all modifiers at once. Instead, the function, trait or type should **explicit bound** over all keywords it could be generic. 143 | 144 | Although being inconvenient to list it manually, this has some advantages over the *generic over all keywords available* syntax. 145 | 146 | ### Explicit 147 | 148 | Readers does not have to remind which keywords are available that may need to be implemented in some specific way. 149 | 150 | ### Backwards compatible to introduce new keywords in the language 151 | 152 | Allowing the *generic over all* means that in case a new keyword lands, all *complete generic* functions and traits may be affected by the keyword, requiring at least some considerations on the side effects. 153 | 154 | ## Limitations 155 | 156 | These are some limitations (hopefully, not yet supported features) noticed in the syntax. 157 | 158 | ### Effect sets 159 | 160 | Function generic over sets of effects, limiting it to be called by **only one** group of effects. 161 | 162 | ```rust 163 | fn compute>() -> Response 164 | where 165 | effect: !alloc + !panic + !async, 166 | effect: alloc + ?async, 167 | effect: const 168 | { 169 | if effect { 170 | // ensures that in "KernelSpace" will not alloc, panic or run futures 171 | } 172 | if effect { 173 | // allow allocations and futures 174 | } 175 | if effect { 176 | // only compile-time evaluation 177 | } 178 | } 179 | 180 | fn caller1() 181 | where 182 | effect: ?alloc + !panic + !async 183 | { 184 | compute>(); // allowed 185 | } 186 | 187 | fn caller2() 188 | where 189 | effect: alloc + async 190 | { 191 | compute>(); // allowed 192 | compute>(); // allowed 193 | } 194 | 195 | fn caller3() 196 | where 197 | effect: !alloc 198 | { 199 | compute>(); // compiler error 200 | } 201 | 202 | fn caller4() 203 | where 204 | effect: const 205 | { 206 | compute>(); // allowed 207 | } 208 | ``` 209 | -------------------------------------------------------------------------------- /archive/evaluation/effects-in-rust/gen.md: -------------------------------------------------------------------------------- 1 | # Iteration (Scoped Effect) 2 | 3 | ## Feature Status 4 | 5 | The `Iterator` trait has been stable in Rust since 1.0, but the generator syntax 6 | is currently _unstable_. This document will assume that generators are created 7 | with the `gen` keyword, but that's for illustrative purposes only. 8 | 9 | ## Description 10 | 11 | todo 12 | 13 | ## Technical Overview 14 | 15 | | Position | Syntax | 16 | | ----------- | --------------- | 17 | | Effect | `gen` | 18 | | Yield | `yield` | 19 | | Apply | N/A | 20 | | Consume | `for..in` | 21 | | Reification | `impl Iterator` | 22 | 23 | ## Refinements 24 | 25 | | Modifier | Description | 26 | | ------------- | ----------------------------------------------------- | 27 | | step | Has a notion of successor and predecessor operations. | 28 | | trusted len † | Reports an accurate length using `size_hint`. | 29 | | trusted step | Upholds all invariants of `Step`. | 30 | | double-ended | Is able to yield elements from both ends. | 31 | | exact size † | Knows its exact length. | 32 | | fused | Always continues to yield `None` when exhausted. | 33 | 34 | > † The difference between `TrustedLen` and `ExactSizeIterator` is that 35 | > `TrustedLen` is marked as `unsafe` to implement while `ExactSizeIterator` is 36 | > marked as _safe_ to implement. This means that if `TrustedLen` is implemented, 37 | > you can rely on it for safety purposes, while with `ExactSizeIterator` you 38 | > cannot. 39 | 40 | ## Positions Available 41 | 42 | | Position | Available | Example | 43 | | ------------------- | --------- | -------------------------------- | 44 | | Manual trait impl | ✅ | `impl Iterator for Cat {}` | 45 | | Free functions | ❌ | `gen fn meow() {}` | 46 | | Inherent functions | ❌ | `impl Cat { gen fn meow() {} } ` | 47 | | Trait methods | ❌ | `trait Cat { gen fn meow() {} }` | 48 | | Trait declarations | ❌ | `gen trait Cat {}` | 49 | | Block scope | ❌ | N/A | 50 | | Argument qualifiers | ❌ | `fn meow(cat: impl gen Cat) {}` | 51 | | Drop | ❌ | `impl gen Drop for Cat {}` | 52 | | Closures | ❌ | `gen ǀǀ {}` | 53 | | Iterators | ❌ | `for cat in cats {}` | 54 | 55 | ## Interactions with other effects 56 | 57 | ### Asynchrony 58 | 59 | 60 | 61 | | Overview | Description | 62 | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | 63 | | Composition | iterator of futures | 64 | | Description | Creates an iterator of futures. The future takes the iterator by `&mut self`, so only a single future may be executed concurrently | 65 | | Example | [`AsyncIterator`][ai] | 66 | | Implementable as of Rust 1.70? | No, async functions in traits are unstable | 67 | 68 | [ai]: https://docs.rs/async-iterator/latest/async_iterator/ 69 | 70 | ### Fallibility 71 | 72 | | Overview | Description | 73 | | ------------------------------ | ------------------------------------------------------------------ | 74 | | Composition | iterator of tryables | 75 | | Description | Creates an iterator of tryables, typically an iterator of `Result` | 76 | | Example | [`FallibleIterator`][fi] | 77 | | Implementable as of Rust 1.70? | No, try in traits is not available | 78 | 79 | [fi]: https://docs.rs/fallible-iterator/latest/fallible_iterator/trait.FallibleIterator.html 80 | 81 | ### Compile-time Execution 82 | 83 | | Overview | Description | 84 | | ------------------------------ | -------------------------------------------------------------- | 85 | | Composition | const iterator | 86 | | Description | Creates an iterator which can be iterated over at compile-time | 87 | | Example | N/A | 88 | | Implementable as of Rust 1.70? | No, const traits are unstable | 89 | 90 | ### Thread-Safety 91 | 92 | | Overview | Description | 93 | | ------------------------------ | --------------------------------------------------------------------------- | 94 | | Composition | iterator of tryables | 95 | | Description | Creates an iterator whose items which can be sent across threads | 96 | | Example | `where I: Iterator, T: Send` | 97 | | Implementable as of Rust 1.70? | Yes, as a bound on use. And by unit-testing the `Send` auto-trait on decls. | 98 | 99 | ### Immovability 100 | | Overview | Description | 101 | | ------------------------------ | --------------------------------------------------------- | 102 | | Composition | an iterator which takes `self: Pin<&mut Self>` | 103 | | Description | An iterator which itself holds onto self-referential data | 104 | | Example | N/A | 105 | | Implementable as of Rust 1.70? | Yes | 106 | 107 | ### Unwinding 108 | 109 | | Overview | Description | 110 | | ------------------------------ | ------------------------------------------------ | 111 | | Composition | iterator may panic instead of yield | 112 | | Description | Creates an iterator which may panic | 113 | | Example | `Iterator` (may panic by default) | 114 | | Implementable as of Rust 1.70? | Yes, but cannot opt-out of "may panic" semantics | 115 | -------------------------------------------------------------------------------- /evaluation/unleakable-types.md: -------------------------------------------------------------------------------- 1 | # Unleakable Types 2 | 3 | ## A trait-based system for unleakable types 4 | 5 | In the [Linear Types 6 | One-Pager](https://blog.yoshuawuyts.com/linear-types-one-pager/) post Yosh 7 | presented a system for types which cannot be leaked. This showed how by 8 | introducing a new auto-trait `Leak`, we could construct a system that would 9 | prevent types from being leaked. 10 | 11 | By preventing types from being leaked, destructors would be guaranteed to run - 12 | which would give allow types in Rust to uphold linear type invariants. Meaning: 13 | destructors could be relied on for the purposes of safety, because some code 14 | will always be run when a type goes out of scope. 15 | 16 | The way to think about this system is as follows: 17 | 18 | 1. We define a new unsafe auto-trait named `Leak` 19 | 2. All bounds take an implicit + Leak bound, like we do for `+ Sized`. 20 | 3. Certain functions such as `mem::forget` will always keep taking `+ Leak` bounds. 21 | 4. Functions which want to opt-in to linearity can take `+ ?Leak` bounds. 22 | 5. Types which want to opt-in to linearity can implement `!Leak` or put a `PhantomLeak` type in a field. 23 | 24 | In code we could see this system expressed as follows: 25 | 26 | ```rust 27 | // Define the trait and create a blanket impl for all types. 28 | // The language would automatically add `+ Leak` bounds to all bounds. 29 | auto trait Leak; 30 | impl Leak for T {} 31 | 32 | // Types are by default assumed to be leakable. 33 | struct Leakable; 34 | 35 | // Mark a type as unleakable, guaranteeing destructors are run 36 | struct Unleakable; 37 | impl !Leak for Unleakable {} 38 | 39 | // A function which requires types implement `Leak`. 40 | // Here `T: Leak` bounds would be assumed by default. 41 | fn will_leak(value: T) {..} 42 | 43 | // A function which operates on types which may or may not leak. 44 | // We're using `?Leak` to opt-out of the automatic `+ Leak` bound. 45 | fn may_leak(value: T) -> T {..} 46 | ``` 47 | 48 | ## The limitation of auto-traits 49 | 50 | In the challenges section of [Linear Types 51 | One-Pager](https://blog.yoshuawuyts.com/linear-types-one-pager/) post, Yosh 52 | remarks the following: 53 | 54 | > We should look at an alternate formulation of these bounds by treating them 55 | > as built-in effects. That would allow us to address the issues of versioning, 56 | > visual noise, etc. in a more consistent and ergonomic way. But that's not a 57 | > requirement to start testing this out. 58 | 59 | The limitations of auto-traits are well-documented, and nobody would be excited 60 | by the prospect of introducing `+ ?Leak` bounds to virtually every bound. For 61 | that reason there was a recommendation to explore alternate effect-based 62 | formulations instead. 63 | 64 | A concrete example of a limitation for `Leak` as an auto-trait is provided by 65 | Saoirse in their posts "Changing the rules of Rust", "Follow up to "Changing 66 | the rules of Rust", and "Generic trait methods and new auto traits". They 67 | provide an example equivalent to the following: 68 | 69 | ```rust 70 | #[edition = 2027] 71 | crate may_leak { 72 | #[leak_compatible] // ← allows bounds to optionally add `+ Leak` 73 | pub trait MayLeak { 74 | fn may_leak(input: T); 75 | } 76 | } 77 | 78 | #[edition = 2024] 79 | crate will_leak { 80 | pub struct WillLeak; 81 | impl super::may_leak::MayLeak for WillLeak { 82 | fn may_leak(input: T) { // ← takes an implicit `+ Leak` bound 83 | core::mem::forget(input); 84 | } 85 | } 86 | } 87 | 88 | #[edition = 2027] 89 | crate may_not_leak { 90 | struct Unleakable; 91 | impl !Leak for Unleakable {} 92 | pub fn may_not_leak() { // ← disables the optional `+ Leak` bound 93 | T::may_leak(Unleakable); 94 | } 95 | } 96 | 97 | // The edition doesn't matter for this function. 98 | fn main() { 99 | may_not_leak::may_not_leak::(); 100 | } 101 | ``` 102 | 103 | Under the rules we provided earlier, when we pass `WillLeak` to `may_not_leak` 104 | it should yield a compile-error. This ends up trying to pass a type which is 105 | `!Leak` to a function which takes an implicit `+ Leak` bound, which shouldn't 106 | compile. 107 | 108 | The limitations of this approach very clearly show up once we consider how this 109 | would be rolled out in practice. Every API which would want to types optionally 110 | leaking would need to add a `+ ?Leak` or `#[maybe(leak)]` annotation to every 111 | parameter which may ever want to leak. This system mixes bespoke attributes 112 | together with auto-traits to create a system very similar to that of `const`. 113 | 114 | ## Reformulating leaking as an effect 115 | 116 | The system of `#[leak_compatible]` and `#[no_leak]` annotations presented is a 117 | bespoke encoding of the general system covered by effect generics. Fundamentally 118 | both effects and trait bounds can be in one of three states: 119 | 120 | - **required**: covered earlier by the implicit `+ Leak` bound, and by `effect T` under effect generics. 121 | - **optional**: covered earlier by the `#[leak_compatible]` annotation, and `#[maybe(effect)]` under effect generics. 122 | - **absent**: covered by the earlier `#[no_leak]` annotation, and `#[no(effect)]` under effect-generics. 123 | 124 | When discussing effects, they can either be opt-in (e.g. `async`, `try`) where 125 | we assume capabilities are not present unless we state we want them. Or opt-out 126 | (e.g. `const`) where we assume capabilities are present, and we opt-out of them 127 | go gain some other property. Currently Rust code may always leak, and what we're 128 | taking away is the ability to leak. Likely the right approach here would be to 129 | name the effect `leak`, and allow people to write both `leak T` and `#[no(leak)] 130 | T`. Under these rules the system would look like this: 131 | 132 | ```rust 133 | // Mark a type as unleakable, guaranteeing destructors are run 134 | #[not(leak)] 135 | struct Unleakable; 136 | 137 | // A type which may be leaked. `leak struct` is assumed, 138 | // but can be written out for clarity. 139 | struct Leakable; 140 | leak struct Leakable; 141 | 142 | // A function which requires all arguments can be leaked. 143 | // `leak fn` is assumed by default, but may be written out for clarity. 144 | fn will_leak(value: T) {..} 145 | leak fn will_leak(value: T) {..} 146 | 147 | // A function which operates on types which may or may not leak. 148 | #[maybe(leak)] 149 | fn may_leak(value: T) -> T {..} 150 | ``` 151 | 152 | Applying this sytem to the longer example would look like this: 153 | 154 | ```rust 155 | #[edition = 2027] 156 | crate may_leak { 157 | #[maybe(leak)] // ← indicates this trait may of may not leak 158 | pub trait MayLeak { 159 | fn may_leak<#[maybe(leak)] T>(input: T); // ← indicates the type in this bound may or may not leak 160 | } 161 | } 162 | 163 | #[edition = 2024] 164 | crate will_leak { 165 | pub struct WillLeak; 166 | impl super::may_leak::MayLeak for WillLeak { 167 | fn may_leak(input: T) { // ← is assumed to be a `leak fn`; assumes `leak T` 168 | core::mem::forget(input); 169 | } 170 | } 171 | } 172 | 173 | #[edition = 2027] 174 | crate may_not_leak { 175 | #[not(leak)] // ← this type may not be leaked 176 | struct Unleakable; 177 | 178 | #[not(leak)] // ← states all bounds take `#[no(leak)]` 179 | pub fn may_not_leak() { 180 | T::may_leak(Unleakable); 181 | } 182 | } 183 | 184 | // The edition doesn't matter for this function. 185 | fn main() { 186 | may_not_leak::may_not_leak::(); 187 | } 188 | ``` 189 | 190 | While similar to the previous design, this version applies a consistent logic 191 | and naming to the bounds and ascriptions following the system laid out by 192 | effect-generics. This would make it so introducing linearity into the type 193 | system wouldn't be its own design with its own attributes, but part of a 194 | consistent framework by which we can evolve the language. 195 | 196 | ## Changing defaults across editions 197 | 198 | An alternative design for `#[not(leak)]` would be to follow the design of the 199 | const keyword more closely, and introduce a positive effect. Perhaps something 200 | like a `linear T` / `linear fn`. However if we assume we will eventually be 201 | successful in the transition to adoption linearity, this would put us in the 202 | awkward position where the ideal system would end up with more ascriptions. 203 | 204 | The beauty of `#[not(leak)]` as the discriminant for linearity is that we could 205 | eventually change the default across editions to not assume leaking is provided, and only if you 206 | want to opt-in to being able to leak you have to add it to your functions. This 207 | is currently already the same for keywords such as `async` and `gen`. Under 208 | these rules, code would be able to change like this: 209 | 210 | ```rust 211 | // All types are assumed to be unleakable by default 212 | struct Unleakable; 213 | 214 | // A type which may be leaked. 215 | leak struct Leakable; 216 | 217 | // A function which requires all types can be leaked. 218 | leak fn will_leak(value: T) {..} 219 | 220 | // A function which operates on types which may or may not leak. 221 | fn may_leak(value: T) -> T {..} 222 | ``` 223 | 224 | Hypothetically a third kind of function could be described where all arguments 225 | are assumed not to leak. But just like we don't (yet?) have a clear use case for 226 | const-only functions, it's unclear that no-leak-only functions would be 227 | beneficial. That said, effect generic provides a consistent framework which 228 | would enable for these to be introduced. 229 | 230 | ## On the choice of keywords 231 | 232 | All keywords and syntax used in this post should be interpreted as placeholders 233 | only. It's hard to show examples if you don't name things, so we've picked some 234 | names to make the example easier to follow. The emphasis of this post is on the 235 | semantics of the system and showing how we can express linear types in a 236 | consistent way by leveraging the framework of effect generics. 237 | 238 | ## References 239 | 240 | - [The pain of Real Linear Types in Rust](https://faultlore.com/blah/linear-rust/) 241 | - [Must move types](https://smallcultfollowing.com/babysteps/blog/2023/03/16/must-move-types/) 242 | - [Linearity and Control](https://blog.yoshuawuyts.com/linearity-and-control/) 243 | - [Linear Types One-Pager](https://blog.yoshuawuyts.com/linear-types-one-pager/) 244 | - [Async destructors, async genericity and completion futures](https://sabrinajewson.org/blog/async-drop) 245 | - [Changing the Rules of Rust](https://without.boats/blog/changing-the-rules-of-rust/) 246 | - [Follow up to "Changing the rules of Rust"](https://without.boats/blog/follow-up-to-changing-the-rules-of-rust/) 247 | - [Generic trait methods and new auto traits](https://without.boats/blog/generic-trait-methods-and-new-auto-traits/) 248 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /evaluation/auto-concurrency.md: -------------------------------------------------------------------------------- 1 | # Auto Concurrency 2 | 3 | Async Rust brings [three unique capabilities to 4 | Rust](https://blog.yoshuawuyts.com/why-async-rust/): the ability to apply ad-hoc 5 | concurrency, the ability to arbitrarily pause, cancel and resume operations, and 6 | finally the ability to combine these capabilities into new ones - such as ad-hoc 7 | timeouts. Async Rust also does one other thing: it decouples "concurrency" from 8 | "parallelism" - while in non-async Rust both are coupled into the "thread" 9 | primitive. 10 | 11 | One challenge however is to make use of these capabilities. People notoriously 12 | struggle to use cancellation correctly, and are often caught off guard that 13 | computations after being suspended at an `.await` point may not necessarily be 14 | resumed ("cancelled"). Similarly: users will often struggle to apply 15 | fine-grained concurrency in their applications - because it fundamentally means 16 | exploding sequential control-flow sequences into Directed Acyclic control-flow 17 | Graphs (control-flow DAGs). 18 | 19 | ## By Example: Swift 20 | 21 | Swift has introduced the `async let` keyword to enable linear-looking 22 | control-flow which statically expands to a concurrent DAG backed by tasks. To 23 | see how this works we can reference 24 | [SE-0304](https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md)'s 25 | example which provides a `makeDinner` routine: 26 | 27 | ```swift 28 | func makeDinner() async throws -> Meal { 29 | async let veggies = chopVegetables() // 1. concurrent with: 2, 3 30 | async let meat = marinateMeat() // 2. concurrent with: 1, 3 31 | async let oven = preheatOven(temperature: 350) // 3. concurrent with: 1, 2, 4 32 | 33 | let dish = Dish(ingredients: await [try veggies, meat]) // 4. depends on: 1, 2, concurrent with: 3 34 | return await oven.cook(dish, duration: .hours(3)) // 5. depends on: 3, 4, not concurrent 35 | } 36 | ``` 37 | 38 | The following constraints and operations occur here: 39 | 40 | - constraint: `dish` depends on `veggies` and `meat`. 41 | - concurrency: `veggies`, `meat`, and `oven` are computed concurrently 42 | - constraint: `Meal` depends on `oven` and `dish` 43 | - concurrency: `oven` and `dish` are computed concurrently 44 | 45 | In Swift the `async let` syntax automatically spawns tasks and ensures that they 46 | resolve when they need to. In Swift `await {}` and `try {}` apply not just to 47 | the top-level expressions but also to all sub-expressions, so for example 48 | awaiting the `oven` is handled by `await oven.cook (..)`. We can translate this 49 | to Rust using the [`futures-concurrency` 50 | library](https://docs.rs/futures-concurrency) without having to use parallel 51 | tasks - just concurrent futures. That would look like this: 52 | 53 | ```rust 54 | use futures_concurrency::prelude::*; 55 | 56 | async fn make_dinner() -> SomeResult { 57 | let dish = { 58 | let veggies = chop_vegetables(); 59 | let meat = marinate_meat(); 60 | let (veggies, meat) = (veggies, meat).try_join().await?; 61 | Dish::new(&[veggies, meat]).await 62 | }; 63 | let (dish, oven) = (dish, preheat_oven(350)).try_join().await?; 64 | oven.cook(dish, Duration::from_mins(3 * 60)).await 65 | } 66 | ``` 67 | 68 | Compared to Swift the control-flow here is much harder to tease apart. We've 69 | accurately described our concurrency DAG; but reversing it to understand 70 | _intent_ has suddenly become a lot harder. Programmers generally have a better 71 | time understanding code when it can be read sequentially; and so it's no 72 | surprise that the Swift version is better at stating intent. 73 | 74 | ## Auto-concurrency for Rust's Async Effect Contexts 75 | 76 | Rust's async system differs a little from Swift's, but only in the details. The 77 | main differences as it comes to what we'd want to do here are three-fold: 78 | 79 | 1. Swift's async primitive are tasks: which are managed, parallel async 80 | primitives. In Rust it's `Future`, which is unmanaged and not parallel by 81 | default - it's only concurrent. 82 | 2. In Rust all `.await` points have to be explicit and recursive awaiting of 83 | expressions is not supported. This is because as mentioned earlier: functions 84 | may permanently yield control flow at `.await` points, and so they have to be 85 | called out in the source code. 86 | 87 | For these reasons we can't quite do what Swift does - but I believe we could 88 | probably do something similar. From a language perspective, it seems like it 89 | should be possible to do a similar system to `async let`. Any number of `async 90 | let` statements can be joined together by the compiler into a single 91 | control-flow graph, as long as their outputs don't depend on each other. And if 92 | we're calling `.await?` on `async let` statements we can even ensure to insert 93 | calls to `try_join` so concurrently executing functions can early abort on 94 | error. 95 | 96 | ```rust 97 | async fn make_dinner() -> SomeResult { 98 | async let veggies = chop_vegetables(); // 1. concurrent with: 2, 3 99 | async let meat = marinate_meat(); // 2. concurrent with: 1, 3 100 | async let oven = preheat_oven(350); // 3. concurrent with: 1, 2, 4 101 | 102 | async let dish = Dish(&[veggies.await?, meat.await?]); // 4. depends on: 1, 2, concurrent with: 3 103 | oven.cook(dish.await, Duration::from_mins(3 * 60)).await // 5. depends on: 3, 4, not concurrent 104 | } 105 | ``` 106 | 107 | Here, just like in the Swift example, we'd achieve concurrency between all 108 | independent steps. And where steps are dependent on one another, they would be 109 | computed as sequential. Each future still needs to be `.await`ed - but in order 110 | to be evaluated concurrently the program authors no longer have to figure it out 111 | by hand. 112 | 113 | If we think about it, this feels like a natural evolution from the principles of 114 | `async/.await`. Just the syntax alone provides us with the ability to convert 115 | complex asynchronous callback graphs into seemingly imperative-looking code. And 116 | by extending that to concurrency too, we're able to reap even more benefits from it. 117 | 118 | ## What about other concurrency operations? 119 | 120 | A brief look at the [`futures-concurrency` 121 | library](https://docs.rs/futures-concurrency/latest/futures_concurrency/) will 122 | reveal a number of concurrency operations. Yet here we're only discussing one: 123 | `Join`. That is because all the other operations do something which is unique to 124 | async code, and so we have to write async code to make full use of it. Whereas 125 | `join` does not semantically change the code: it just takes independent 126 | sequential operations and runs them in concert. 127 | 128 | ## Maybe-async and auto-concurrency 129 | 130 | The main premise of `#[maybe(async)]` notations is that they can take sequential 131 | code and optionally run them without blocking. Under the system described in 132 | this post that code could not only be non-blocking, it could also be concurrent. 133 | Taking the system we're describing in the "Effect Generic Function Bodies and 134 | Bounds" draft, we could write our `async let`-based code example as follows to 135 | make it conditional over the `async` effect: 136 | 137 | ```rust 138 | #[maybe(async)] // <- changed `async fn` to `#[maybe(async)] fn` 139 | fn make_dinner() -> SomeResult { 140 | async let veggies = chop_vegetables(); 141 | async let meat = marinate_meat(); 142 | async let oven = preheat_oven(350); 143 | 144 | async let dish = Dish(&[veggies.await?, meat.await?]); 145 | oven.cook(dish.await, Duration::from_mins(3 * 60)).await 146 | } 147 | ``` 148 | 149 | Which when evaluated synchronously would be lowered to the following code. This 150 | code blocks and runs sequentially, but that is the best we can do without async 151 | Rust's ad-hoc async capabilities. 152 | 153 | ```rust 154 | fn make_dinner() -> SomeResult { 155 | let veggies = chop_vegetables(); 156 | let meat = marinate_meat(); 157 | let oven = preheat_oven(350); 158 | 159 | let dish = Dish(&[veggies?, meat?]); 160 | oven.cook(dish, Duration::from_mins(3 * 60)) 161 | } 162 | ``` 163 | 164 | This is not the only way that `#[maybe(async)]` code could leverage async 165 | concurrency operations: an async version of 166 | [`const_eval_select`](https://doc.rust-lang.org/std/intrinsics/fn.const_eval_select.html) 167 | would also work. It would, however, be by far the most convenient way of 168 | creating parity between both contexts. As well as make async Rust code that much 169 | easier to read. 170 | 171 | ## A note on syntax 172 | 173 | An earlier version of this document proposed using `.co.await`, `.co_await`, 174 | just `.co` or some other keyword to take the place of `async let` to indicate a 175 | concurrent `.await` can happen. The feasibility of syntax like that is not 176 | clear; though there would likely be distinct benefits to preserving the postfix 177 | nature of existing notations. Any further exploration of this direction should 178 | consider alternate syntaxes to `async let`. In particular as concurrent 179 | execution of `for await` loops is something that's also desirable, and would 180 | likely want syntax parity with concurrent execution of futures. 181 | 182 | ## Conclusion 183 | 184 | In this document we describe a mechanism inspired by Swift's `async let` 185 | primitive to author imperative-looking code which is lowered into concurrent, 186 | unmanaged futures. Rather than needing to manually convert linear code into a 187 | concurrent directed graph, the compiler could do that for us. Here is an example 188 | code as we would write it today using the 189 | [`Join::join`](https://docs.rs/futures-concurrency/latest/futures_concurrency/future/trait.Join.html) 190 | operation, compared to a high-level `async let` based variant which would 191 | desugar into the same code. 192 | 193 | ```rust 194 | /// A manual concurrent implementation using Rust 1.76 today. 195 | async fn make_dinner() -> SomeResult { 196 | let dish = { 197 | let veggies = chop_vegetables(); 198 | let meat = marinate_meat(); 199 | let (veggies, meat) = (veggies, meat).try_join().await?; 200 | Dish::new(&[veggies, meat]).await 201 | }; 202 | let (dish, oven) = (dish, preheat_oven(350)).try_join().await?; 203 | oven.cook(dish, Duration::from_mins(3 * 60)).await 204 | } 205 | 206 | /// An automatic concurrent implementation using a hypothetical `async let` 207 | /// feature. This would desugar into equivalent code as the manual example. 208 | async fn make_dinner() -> SomeResult { 209 | async let veggies = chop_vegetables(); // 1. concurrent with: 2, 3 210 | async let meat = marinate_meat(); // 2. concurrent with: 1, 3 211 | async let oven = preheat_oven(350); // 3. concurrent with: 1, 2, 4 212 | 213 | async let dish = Dish(&[veggies.await?, meat.await?]); // 4. depends on: 1, 2, concurrent with: 3 214 | oven.cook(dish.await, Duration::from_mins(3 * 60)).await // 5. depends on: 3, 4, not concurrent 215 | } 216 | ``` 217 | 218 | This is not the first proposal to suggest an some form of concurrent notation 219 | for async Rust; to our knowledge that would be Conrad Ludgate in their [async 220 | let blog post](https://conradludgate.com/posts/async-let). However just like in 221 | Swift it seems to be based on the idea of managed multi-threaded tasks - not 222 | Rust's unmanaged, lightweight futures primitive. 223 | 224 | A version of this is likely possible for multi-threaded code too; ostensibly via 225 | some kind of `par` keyword (`par let` / `par for await..in`). A full design is out 226 | of scope for this post; but it should be possible to improve Rust's parallel 227 | system in both async and non-async Rust alike (using tasks and threads 228 | respectively). 229 | 230 | ## References 231 | 232 | - [Swift SE-0304: Structured Concurrency](https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md) 233 | - [Conrad Ludgate: Async Let - A New Concurrency Primitive?](https://conradludgate.com/posts/async-let) 234 | -------------------------------------------------------------------------------- /evaluation/pattern-types.md: -------------------------------------------------------------------------------- 1 | # Pattern Types and Backwards Compatibility 2 | 3 | ## Introduction 4 | 5 | [Pattern types](https://gist.github.com/joboet/0cecbce925ee2ad1ee3e5520cec81e30) 6 | are an in-progress proposal for Rust to add a limited form of refinement types / 7 | liquid types to Rust via pattern the pattern notation. Take for example the 8 | existing 9 | [`AtomicBool::load`](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicBool.html#method.load) 10 | operation. Its signature looks like this: 11 | 12 | ```rust 13 | /// A boolean type which can be safely shared between threads. 14 | struct AtomicBool { .. } 15 | 16 | impl AtomicBool { 17 | /// Loads a value from the bool. 18 | pub fn load(&self, order: Ordering) -> bool { .. } 19 | } 20 | ``` 21 | 22 | Atomics are part Rust's memory model, and are how we're able to share data 23 | between threads. Depending on what we want to do with an atomic, we'll want to 24 | give it a different 25 | [`Ordering`](https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html) 26 | argument. `Ordering` is just an enum, which has the following variants: 27 | 28 | ```rust 29 | #[non_exhaustive] 30 | pub enum Ordering { 31 | Relaxed, 32 | Release, 33 | Acquire, 34 | AcqRel, 35 | SeqCst, 36 | } 37 | ``` 38 | 39 | For this example it doesn't exactly matter what each of these variants are for. 40 | But what's important is that not all variants are valid arguments for 41 | `AtomicBool::load`. Its documentation says that `SeqCst`, `Acquire`, and 42 | `Relaxed` are valid. But if the `Release` or `AcqRel` variants are used, it will 43 | panic at runtime. 44 | 45 | Pattern types would in theory enable us to "shift-left" on this, by encoding the 46 | allowed variants directly into the function's parameters. This would encode this 47 | invariant directly via the type system, meaning we've "shifted left" from a 48 | runtime error (e.g. we need to run tests to find the bug), to a compiler error 49 | (e.g. we need to run `cargo check` to find the bug). Using the pattern types 50 | draft RFC, this would look something like this: 51 | 52 | ```rust 53 | struct AtomicBool { .. } 54 | impl AtomicBool { 55 | pub fn load(&self, 56 | order: Ordering is Ordering::SeqCst | Ordering::Acquire | Ordering::Relaxed 57 | ) -> bool { .. } 58 | } 59 | ``` 60 | 61 | ## Backwards-compatibility issues 62 | 63 | Moving checks from runtime to compile-time is generally considered a good thing, 64 | as it shortens the time it takes to discover bugs. But when we use pattern types 65 | as inputs to functions, we're *constraining* the input space from all variants 66 | to just the legal variants. Take for example the following code, which is legal 67 | to write today. 68 | 69 | ```rust 70 | pub fn load_wrapper(order: Ordering, bool: &AtomicBool) -> bool { 71 | bool.load(order) 72 | } 73 | ``` 74 | 75 | This code does not know about pattern types, and Rust's backwards-compatibility 76 | guarantees require that it keeps compiling in future releases of the compiler. 77 | That means that changing `AtomicBool::load` to require taking pattern types as 78 | its input would be a backwards-incompatible change. So we cannot just do that. 79 | 80 | One alternative would be to create a duplicate version of `AtomicBool` which 81 | does know how to take pattern types. But duplicating code just to improve it 82 | feels pretty bad - instead it would be nice if we could update existing 83 | functions without it leading to breaking changes. 84 | 85 | ## Resolving the backwards-compatibility issues 86 | 87 | On Zulip people have brought up the idea of using editions to resolve these 88 | issues. That might be possible, but it would mean a clean break between code 89 | written on an older edition, and code written on a newer edition. And while we 90 | can leverage editions to change defaults in the language, this kind of break 91 | feels like it would push against the intended goal of maintaining compatibility 92 | between editions. 93 | 94 | The idea underlying it seems right though: we do want some way to express 95 | *modality* in our type system. We've already done this before using the `const` 96 | effect. Functions tagged as `const` can be evaluated either during compilation 97 | or at runtime. And it's backwards-compatible to take an existing runtime-only 98 | `fn` and change it to a `const fn`. 99 | 100 | ```rust 101 | fn meow() -> &'static str { "meow" } // 1. The base `fn meow` 102 | const fn meow() -> &'static str { "meow" } // 2. Changing `meow` to a `const fn` is backwards-compatible 103 | ``` 104 | 105 | We could do something very similar with pattern types as well. The base 106 | mechanism for this is to define a function which can be compiled in one of two 107 | modes: 108 | 109 | 1. **Invariants are evaluated at compile-time**: The pattern types are evaluated by 110 | the compiler according to the pattern type RFC. A compiler error is raised if 111 | the pattern's invariants are violated. 112 | 2. **Invariants are evaluated at runtime**: The pattern types are converted to a 113 | sequence of assertions, and evaluated at runtime. 114 | 115 | The translation here from pattern types to runtime assertions should be fairly 116 | mechanical. We could imagine some notation which signals that while a function 117 | may declare pattern types, the caller has an option to either evaluate them at 118 | runtime or during compilation. Taking our earlier `AtomicBool::load` example, we 119 | could imagine something like this: 120 | 121 | ```rust 122 | struct AtomicBool { .. } 123 | impl AtomicBool { 124 | #[maybe(pattern_types)] 125 | pub fn load(&self, 126 | order: Ordering is Ordering::SeqCst | Ordering::Acquire | Ordering::Relaxed 127 | ) -> bool { .. } 128 | } 129 | ``` 130 | 131 | With this notation, all existing uses of `AtomicBool::load` would continue 132 | working. But optionally it could be called using pattern types, which would be 133 | evaluated at compile-time. Depending on which variant of the function is 134 | selected, the lowering of the function would change. Desugared, this would 135 | roughly look like this: 136 | 137 | ```rust 138 | /// Semantic lowering of `AtomicBool::load` 139 | /// using compile-time checks 140 | pub fn load(&self, 141 | order: Ordering is Ordering::SeqCst | Ordering::Acquire | Ordering::Relaxed 142 | ) -> bool { .. } 143 | 144 | /// Semantic lowering of `AtomicBool::load` 145 | /// using runtime assertions 146 | pub fn load(&self, order: Ordering) -> bool { 147 | match order { 148 | order @ Ordering::SeqCst | Ordering::Acquire | Ordering::Relaxed => .., 149 | order => panic!("Expected `Ordering::{{Acquire | Relaxed | SeqCst}}`, received {order}"), 150 | } 151 | } 152 | ``` 153 | 154 | ## TODO: Effect logic and notation 155 | 156 | - in its base there are four states possible: `always | never | maybe | unknown` 157 | - `maybe(pattern_types)` is a backwards-compatibility guarantee. Having logical `never` 158 | + `always` markers will put us in a position where we can eventually pull the 159 | lever across an edition to default all functions to default to always using 160 | pattern types - without breaking any existing code or breaking code compat. 161 | - unlike `maybe(async)`, by lowering to runtime checks functions which use 162 | `maybe` patterns should always be able to call functions which take `always` 163 | patterns - runtime assertions using `match` will be enough to shrink 164 | the input state to be valid from that point onward 165 | - the relation to subtyping and return types will affect which states of this system we may want to encode 166 | - unclear what the benefits are for a strictly "always subtyping" notation 167 | - in practice we'll want to independently gate the stabilization of pattern 168 | types for existing stdlib APIs - which means we need a labeling system in the compiler 169 | 170 | ## Example: how to combine effect states for pattern types 171 | 172 | [Nadrieril](https://github.com/Nadrieril) asked the following question: 173 | 174 | > Consider the case where crate A uses compile-time checks for pattern types and 175 | > crate B uses crate A but has no knowledge of pattern types. If we encode this 176 | > choice as an effect, we must be careful not to bubble it up (as effects do) to a 177 | > function that has no knowledge of pattern types. 178 | 179 | Let's write this example out. We're going to write three functions: one which 180 | always uses pattern types, one which may use pattern types, and a function which 181 | doesn't use pattern types. They all call each other, and that should Just Work. 182 | Let's start with the always-pattern function. 183 | 184 | ```rust 185 | /// This function always evaluates pattern 186 | /// types at compile-time. 187 | fn always(num: u8 is 0..10) { 188 | println!("received number {num}"); 189 | } 190 | ``` 191 | 192 | There's nothing too special about this function: it always takes a pattern type, 193 | meaning we can't just give it any `u8` - it needs to fit the pattern. Next, 194 | let's write out a maybe-pattern function which either takes a pattern or a base 195 | type - and depending on which variant is passed will either validate the input 196 | during compilation or at runtime. This will then call into our `always` function. 197 | 198 | 199 | ```rust 200 | /// This function can evaluate pattern types 201 | /// either at compile-time or at runtime 202 | #[maybe(pattern_types)] 203 | fn maybe(num: u8 is 0..10) { 204 | always(num); 205 | } 206 | ``` 207 | 208 | This function either evaluates patterns during compilation or at runtime. As 209 | we've seen before: if a pattern is evaluated at runtime, it will effectively 210 | work as a `match` + `panic!`. As a result this function guarantees it will 211 | *always* validate its inputs, meaning once we gain access to `num` in the 212 | function body it will always conform to the pattern. And so we have no problem 213 | calling the `always` function. 214 | 215 | Next up is our function `never`, which never evaluates patterns. It takes a bare 216 | `u8` with no restrictions on it whatsoever. It should be able to call the 217 | `maybe` function without an issue. 218 | 219 | ```rust 220 | /// This function does not reason about pattern types 221 | fn never(num: u8) { 222 | maybe(num); 223 | } 224 | ``` 225 | 226 | But if we try calling the `always` function from `never`, we run into issues: 227 | 228 | ```rust 229 | /// This function does not reason about pattern types 230 | fn never(num: u8) { 231 | always(num); // ❌ compiler error 232 | } 233 | ``` 234 | This should result in a compiler error along these lines: 235 | 236 | ```text 237 | error[E0308]: mismatched types 238 | --> src/lib.rs:4:12 239 | | 240 | 4 | always(num); 241 | | ------ ^^^^^ expected `u8 is 0..10`, found `u8` 242 | | | 243 | | arguments to this function are incorrect 244 | ``` 245 | 246 | The easiest way to resolve this would be to rewrite the `never` function to take 247 | the same signature as the `maybe` function. This would insert the correct 248 | runtime checks, contraining the value to the right pattern, which as we've seen 249 | would make it possible to call the `always` function without any issues. 250 | 251 | ## How widespread is this? 252 | 253 | Maintaining strict backwards-compatibility is primarily a concern for the Rust 254 | stdlib. While it might be difficult to create major versions for certain other 255 | codebases, the Rust stdlib is in the unique position that it is both used by 256 | everyone, and we can never break existing APIs. So when we're looking at using 257 | pattern types in input positions, it's okay to assume the Rust stdlib will be 258 | the main user of it. To date we know of at least the following APIs which would 259 | want to leverage pattern types as inputs: 260 | 261 | - __number primitives__: Number types in Rust expose a wide range of operations. 262 | Take for example a look at the [`u8` 263 | type](https://doc.rust-lang.org/std/primitive.u8.html). It exposes around 20 264 | operations per type which will panic if certain number ranges are passed. 265 | - __atomics__: this is the example we've been using in this post. Atomic 266 | operations take an `Ordering` enum, where each operation can only take certain 267 | variants of that enum. Being able to check that during compilation would be a 268 | boon. 269 | - __iterator methods__: For example 270 | [`Iterator::step_by`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.step_by) 271 | currently takes a `usize`, but would want to take a `usize is 1..`. The same is 272 | true for the unstable `Iterator::array_chunks` and `Iterator::map_windows`. 273 | 274 | ## A note on subtyping 275 | 276 | So far we have assumed that pattern types will subtype. That means that if we 277 | have a `u32 is 0..10`, we can pass that anywhere a `u32` is accepted. And if we 278 | have a function that returns a `u32`, it would not be a breaking change to 279 | restrict that to become a pattern. Enabling patterns to subtype would be 280 | complicated, and may not be reasonably possible. If that is the case, then 281 | changing any argument or return type in any existing API would be 282 | backwards-incompatible. 283 | 284 | Even if types don't strictly sub-type, it is likely still going to be possible 285 | to cast from pattern types back to their base types since it's infallible and 286 | should be supported by the language. That means the following would likely be 287 | supported: 288 | 289 | ```rust 290 | let x: u8 as 0..10 = 2; 291 | let x: u8 = x as u8; 292 | ``` 293 | -------------------------------------------------------------------------------- /evaluation/syntax/const-bool-like-effects.md: -------------------------------------------------------------------------------- 1 | - Name: `const bool-like effects` 2 | - Proposed by: [@Lili Zoey](https://github.com/sayaks) 3 | - Original proposal: [comment](https://github.com/rust-lang/keyword-generics-initiative/issues/10#issuecomment-1445263558) 4 | 5 | # Design 6 | 7 | 9 | 10 | ## base (reference) 11 | 12 | 14 | 15 | ```rust 16 | /// A trimmed-down version of the `std::Iterator` trait. 17 | pub trait Iterator { 18 | type Item; 19 | fn next(&mut self) -> Option; 20 | fn size_hint(&self) -> (usize, Option); 21 | } 22 | 23 | /// An adaptation of `Iterator::find` to a free-function 24 | pub fn find(iter: &mut I, predicate: P) -> Option 25 | where 26 | I: Iterator + Sized, 27 | P: FnMut(&T) -> bool; 28 | ``` 29 | 30 | ## always async 31 | 32 | 33 | In all 34 | The methods on the trait are assumed async because the trait is `async`. 35 | 36 | Variation A: 37 | 38 | ```rust 39 | pub async trait Iterator { 40 | type Item; 41 | fn next(&mut self) -> Option; 42 | !async fn size_hint(&self) -> (usize, Option); 43 | } 44 | 45 | pub async fn find(iter: &mut I, predicate: P) -> Option 46 | where 47 | I: Iterator + Sized, 48 | P: async FnMut(&T) -> bool; 49 | ``` 50 | 51 | Variation B. Using an "`effect`-generics" notation: 52 | 53 | ```rust 54 | pub trait Iterator { 55 | type Item; 56 | fn next(&mut self) -> Option; 57 | fn size_hint(&self) -> (usize, Option); 58 | } 59 | 60 | pub fn find(iter: &mut I, predicate: P) -> Option 61 | where 62 | I: Iterator + Sized, 63 | P: FnMut(&T) -> bool; 64 | ``` 65 | 66 | Variation C. Using an `effect`-notation in `where`-bounds: 67 | 68 | ```rust 69 | pub trait Iterator 70 | where 71 | effect async 72 | { 73 | type Item; 74 | fn next(&mut self) -> Option; 75 | fn size_hint(&self) -> (usize, Option) 76 | where 77 | effect !async; 78 | } 79 | 80 | pub fn find(iter: &mut I, predicate: P) -> Option 81 | where 82 | I: Iterator + Sized, 83 | P: FnMut(&T) -> bool; 84 | ``` 85 | 86 | ## maybe async 87 | 88 | 89 | 90 | For all variations the use of `` on `fn next` is elided. 91 | 92 | Variation A. Using an `effect A: async` + `!async fn` in the trait definition: 93 | 94 | ```rust 95 | pub trait Iterator { 96 | type Item; 97 | fn next(&mut self) -> Option; 98 | !async fn size_hint(&self) -> (usize, Option); 99 | } 100 | 101 | pub fn find(iter: &mut I, predicate: P) -> Option 102 | where 103 | I: Iterator + Sized, 104 | P: FnMut(&T) -> bool; 105 | ``` 106 | 107 | Variation B. Using `effect A: async` + `effect! async` in the trait definition: 108 | 109 | ```rust 110 | pub trait Iterator { 111 | type Item; 112 | fn next(&mut self) -> Option; 113 | fn size_hint(&self) -> (usize, Option); 114 | } 115 | 116 | pub fn find(iter: &mut I, predicate: P) -> Option 117 | where 118 | I: Iterator + Sized, 119 | P: FnMut(&T) -> bool; 120 | ``` 121 | 122 | Variation C. Using `effect A: async` + `where effect !async` notation. If we'd 123 | instead written `where A = !async`, the `size_hint` method would only exist if 124 | the context was not async. It instead now exists as not async in all contexts: 125 | 126 | ```rust 127 | pub trait Iterator { 128 | type Item; 129 | fn next(&mut self) -> Option; 130 | fn size_hint(&self) -> (usize, Option) 131 | where 132 | effect !async; 133 | } 134 | 135 | pub fn find(iter: &mut I, predicate: P) -> Option 136 | where 137 | I: Iterator + Sized, 138 | P: FnMut(&T) -> bool; 139 | ``` 140 | 141 | ## generic over all modifier keywords 142 | 143 | 145 | 146 | ```rust 147 | pub trait Iterator> { 148 | type Item; 149 | fn next(&mut self) -> Option; 150 | !async fn size_hint(&self) -> (usize, Option); 151 | } 152 | 153 | pub fn find>(iter: &mut I, predicate: P) -> Option 154 | where 155 | I: Iterator = A> + Sized, 156 | P: FnMut = A>(&T) -> bool; 157 | ``` 158 | [See also](#foreffect-bounds-and-traits) 159 | 160 | # Notes 161 | `!async fn foo` could be `sync fn foo` or omitted entirely in favor of only having `fn foo`. It is also a question if *all* effects should allow for `effect fn foo` syntax. 162 | 163 | `for` should maybe be made more special-looking since it behaves quite differently from other generic effect variables. 164 | 165 | The exact syntax of `effect A: E` and `effect E = A` for declaring a generic and specifying a bound for an effect could maybe be made different. 166 | 167 | It might be easier to implement specialization for specifically effect-generics, as they are rather simple, effectively just being bools, and there not being any lifetime parameters on them. 168 | 169 | ## Some nice things about the syntax 170 | 171 | ### Specific behavior 172 | To make a function have specific behavior in the case where an effect is or is not true, we could do this: 173 | ```rs 174 | fn foo() { 175 | if A { 176 | // do stuff when foo is async 177 | } else { 178 | // do stuff when foo is not async 179 | } 180 | } 181 | ``` 182 | 183 | ### Impl blocks 184 | impl blocks could look very similar to any other generics. 185 | ```rust 186 | impl SomeTrait MyGenericType { ... } 187 | impl SomeTrait MyAsyncType { ... } 188 | impl SomeTrait MySyncType { ... } 189 | ``` 190 | 191 | ## Description 192 | We can add effects to generics like ``, and create bounds for the effects of types by doing `effect E = A` in the `<..>` list or the where-clause. 193 | 194 | The basic syntax is that `effect async = true` means the type is async, whereas `effect async = false` means it is not. 195 | 196 | For convenience we'd let `effect async` be the same as `effect async = true` and `effect !async` be the same as `effect async = false`. 197 | 198 | `async fn foo` would be syntactic sugar for `fn foo`. and similar for other effects. 199 | 200 | So as an example, here are some equivalent ways of writing an async function: 201 | ```rust 202 | fn foo(...) {...} 203 | fn foo(...) {...} 204 | async fn foo(...) {...} 205 | fn foo(...) where effect async {...} 206 | ``` 207 | 208 | Every effect has a default value, and if there is no bound on the type for that specific effect it is assumed to have its default value. So the function above, having no bound on `const`, would be assumed not-const. 209 | 210 | This could be explicitly stated like 211 | ```rust 212 | async fn foo(...) where effect !const {...} 213 | ``` 214 | However this would be unneccesary. 215 | 216 | If a type has only one generic for an effect, and no other bounds for that effect. It is assumed to have the same bound as that one generic. Meaning the following are equivalent ways of making a function generic over `async`. 217 | ```rust 218 | fn foofoo(...) where effect async = A {...} 219 | fn foofoo(...) {...} 220 | ``` 221 | 222 | However if there are multiple generics, we'd need to explicitly state what the bound should be for the type itself. 223 | ```rust 224 | fn foofoo(...) where effect async = A | B {...} 225 | ``` 226 | This would mean that `foo` is async if either `A` is true or `B` is true. We could also use `A + B` if wanted it to be async whenever both are true. 227 | 228 | Declaring an type to have/not have an effect different from the default value might change the type. For instance 229 | `fn foo() -> T` would become `foo() -> Future`. 230 | 231 | Every generic effect variable (except `for`) is also like a constant boolean value, which is true whenever the type is in a context where it has that effect, and false otherwise. 232 | 233 | In traits, the items are assumed to have the same effect bounds as the trait itself. But this can be overridden using specific bounds for that item. 234 | 235 | For instance 236 | ```rs 237 | trait Read { 238 | // This function is now generic over async 239 | fn read(&mut self, buf: &mut [u8]) -> Result; 240 | // or equivalently 241 | fn read(&mut self, buf: &mut [u8]) -> Result where effect async = A; 242 | 243 | // This function is now always async 244 | async fn read(&mut self, buf: &mut [u8]) -> Result; 245 | // or equivalently 246 | fn read(&mut self, buf: &mut [u8]) -> Result where effect async; 247 | 248 | // This function now only exists when the trait is async 249 | fn read(&mut self, buf: &mut [u8]) -> Result where A; 250 | } 251 | ``` 252 | 253 | This also shows that unlike normal `const _: bool` we can actually use whether the generic effects are `true`/`false` in the where-clause. 254 | 255 | ### `for` 256 | 257 | `for` is a universal effect bound that allows you to place bounds on all the effects of a type. Adding a `effect A: for` makes `A` a generic variable that ranges over every effect. This means its value is no longer a simple `true`/`false` and so can't be used bare in where-clauses. 258 | 259 | If another bound is added that is more specific, that bound will limit the possible values of `A` as well. Meaning that if you have `, effect async>`, we would have the type be generic over every effect except async. And the type would always be async. 260 | 261 | For instance, to make a function generic over all effects except const we'd write 262 | ```rust 263 | fn foo>(...) where effect async {...} 264 | ``` 265 | 266 | To place bounds on every effect we write `for = A` where `A` is some bound. This should probably be limited somewhat to avoid people writing code that can very easily break. Consider for instance `for = true`, which would declare something as having *every* effect. This could lead to breakage if a new effect is added and the function isn't compatible with this new effect. The main uses of placing bounds on `for` would to use it with other universal bounds. 267 | 268 | Using `A + B` and `A | B` bounds for universal bounds may also be problematic, as it may not always be possible to create any meaningful code that is generic in all those cases. So we may have to either disallow having multiple generic universal bounds, or have the compiler automatically infer the relationship between effects. 269 | 270 | For instance 271 | ```rust 272 | fn foo, effect B: for>(closure1: F1, closure2: F2) -> O 273 | where 274 | F1: FnMut = A>() -> O, 275 | F2: FnMut = B>() -> O 276 | { ... } 277 | ``` 278 | Here it is unclear when `foo` should be async and const. For instance, usually a function is `async` if there is *any* async code in the function. Whereas it is `const` if *all* the code is `const`. 279 | 280 | I'm not entirely sure if this is best left up to the compiler to infer, it should be disallowed, or if the user must specify the bounds on every specific effect they may use. 281 | 282 | However if the compiler infers it all, we could still specify specific relationships, like: 283 | ```rust 284 | fn foo, effect B: for>(closure1: F1, closure2: F2) -> O 285 | where 286 | effect async = A + B, 287 | F1: FnMut = A>() -> O, 288 | F2: FnMut = B>() -> O 289 | { ... } 290 | ``` 291 | To make this function async only if *both* `A` and `B` are async (or rather `async = true` in both sets `A` and `B`). 292 | 293 | ### semi-formal description 294 |

295 | Syntax 296 | There's a new kind of generic called effect-generics. For any given type, that effect may be `true` meaning the type has that effect, or it can be `false` meaning the type does not have that effect. 297 | 298 | We can make a type generic over an effect by adding `effect A: E`, where `A` is a generic variable and `E` is an effect. 299 | 300 | An effect bound is one of: `true`, `false`, `default`, `A`, `B1 + B2`, `B1 | B2`, `!B1`. Where `A` is a generic variable, `B1` and `B2` are effect bounds. 301 | 302 | An effect is either: the name of an effect, a generic variable, or `for` 303 | 304 | To specify that a type must fit some effect bound we write `effect E = A`, where `E` is an effect and `A` is an effect bound, either in the `<..>` list or in the where-clause. 305 |
306 | 307 |
308 | Semantics 309 | 310 | - `effect E = true` means "has the effect `E`" 311 | - `effect E = false` means "does not have the effect `E`" 312 | - `effect E = default` means "has the effect `E` if the default for the effect is true" 313 | - `effect E = A` where `A` is a generic variable, means "has the effect `E` if `A` is true" 314 | - `effect E = B1 + B2` means "has the effect `E` if the bounds `B1` and `B2` are true" 315 | - `effect E = B1 | B2` means "has the effect `E` if the bounds `B1` or `B2` are true" 316 | - `effect E = !B` means "has the effect `E` if the bound `B` is false" 317 | - `effect for = B` means "the effect bound `B` applies to every effect" 318 | - `effect A: E` means "`A` is a generic variable corresponding to the effect `E`" 319 | 320 |
321 | 322 | ## `for` bounds and traits 323 | In the [generic over all keywords](#generic-over-all-modifier-keywords) case we'd have that `size_hint` is generic over all effects except async. So it might be better to make such universal bounds not automatically apply to all items in a trait. 324 | 325 | In that case we'd have 326 | ```rust 327 | pub trait Iterator> { 328 | type Item; 329 | fn next(&mut self) -> Option where for = A; 330 | fn size_hint(&self) -> (usize, Option); 331 | } 332 | ``` 333 | Alternatively we could have an opt-out syntax, which would look something like 334 | ```rust 335 | pub trait Iterator> { 336 | type Item; 337 | fn next(&mut self) -> Option; 338 | fn size_hint(&self) -> (usize, Option) where for = default; 339 | } 340 | ``` 341 | 342 | -------------------------------------------------------------------------------- /updates/2023-02-23-keyword-generics-progress-report-feb-2023.md: -------------------------------------------------------------------------------- 1 | # Progress Report February 2023 2 | 3 | _This post was originally posted on the [Inside Rust 4 | Blog](https://blog.rust-lang.org/inside-rust/2023/02/23/keyword-generics-progress-report-feb-2023.html), 5 | but is included in this repository to be more easily referenced._ 6 | 7 | About 9 months ago [we announced][announce] the creation of the Keyword Generics 8 | Initiative; a group working under the lang team with the intent to solve the 9 | [function coloring problem][color] [^color] through the type system not just for 10 | `async`, but for `const` and all current and future function modifier keywords 11 | as well. 12 | 13 | We're happy to share that we've made a lot of progress over these last several 14 | months, and we're finally ready to start putting some of our designs forward through 15 | RFCs. Because it's been a while since our last update, and because we're excited 16 | to share what we've been working on, in this post we'll be going over some of the things 17 | we're planning to propose. 18 | 19 | [announce]: https://blog.rust-lang.org/inside-rust/2022/07/27/keyword-generics.html 20 | [color]: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ 21 | 22 | [^color]: To briefly recap this problem: you can't call an `async fn` from a 23 | non-async fn. This makes the "async" notation go viral, as every function that 24 | calls it also needs to be async. But we believe possibly more importantly: it 25 | requires a duplication of most stdlib types and ecosystem libraries. Instead we 26 | suspected we might be able to overcome this issue by introducing a new kind of 27 | generic which would enable functions and types to be "generic" over whether 28 | they're async or not, const or not, etc. 29 | 30 | ## An async example 31 | 32 | In our [previous post][announce] we introduced the placeholder `async` syntax to describe the 33 | concept of a "function which is generic over its asyncness". We always knew we 34 | wanted something that felt lighter weight than that, so in for our current design 35 | we've chosen to drop the notion of a generic parameter for the end-user syntax, 36 | and instead picked the `?async` notation. We've borrowed this from the trait 37 | system, where for example `+ ?Sized` indicates that something may or may not 38 | implement the `Sized` trait. Similarly `?async` means a function may or may not be 39 | async. We also refer to these as "maybe-async" functions. 40 | 41 | Time for an example. Say we took the [`Read` trait][read] and the 42 | [read_to_string_methods][rts]. In the stdlib their implementations look somewhat 43 | like this today: 44 | 45 | ```rust 46 | trait Read { 47 | fn read(&mut self, buf: &mut [u8]) -> Result; 48 | fn read_to_string(&mut self, buf: &mut String) -> Result { ... } 49 | } 50 | 51 | /// Read from a reader into a string. 52 | fn read_to_string(reader: &mut impl Read) -> std::io::Result { 53 | let mut string = String::new(); 54 | reader.read_to_string(&mut string)?; 55 | Ok(string) 56 | } 57 | ``` 58 | 59 | Now, what if we wanted to make these async in the future? Using `?async` 60 | notation we could change them to look like this: 61 | 62 | ```rust 63 | trait ?async Read { 64 | ?async fn read(&mut self, buf: &mut [u8]) -> Result; 65 | ?async fn read_to_string(&mut self, buf: &mut String) -> Result { ... } 66 | } 67 | 68 | /// Read from a reader into a string. 69 | ?async fn read_to_string(reader: &mut impl ?async Read) -> std::io::Result { 70 | let mut string = String::new(); 71 | reader.read_to_string(&mut string).await?; 72 | Ok(string) 73 | } 74 | ``` 75 | 76 | The way this would work is that `Read` and `read_to_string` would become generic over 77 | their "asyncness". When compiled for an `async` context, they will behave 78 | asynchronously. When compiled in a non-async context, they will behave 79 | synchronously. The `.await` in the `read_to_string` function body is necessary 80 | to mark the cancellation point in case the function is compiled as async; but 81 | when not async would essentially become a no-op [^always-async-maybe]: 82 | 83 | [^always-async-maybe]: One restriction `?async` contexts have is that they can 84 | only call other `?async` and non-`async` functions. Because if we could call an 85 | always-`async` function, there would be no clear right thing to do when compiled 86 | in non-async mode. So things like async concurrency operations won't directly 87 | work in always-async contexts. But we have a way out of this we talk about later 88 | in the post: `if is_async() .. else ..`. This allows you to branch the body of a 89 | `?async fn` based on which mode it's being compiled in, and will allow you to 90 | write different logic for async and non-async modes. This means you can choose 91 | to use async concurrency in the async version, but keep things sequential in the 92 | non-async version. 93 | 94 | ```rust 95 | // `read_to_string` is inferred to be `!async` because 96 | // we didn't `.await` it, nor expected a future of any kind. 97 | #[test] 98 | fn sync_call() { 99 | let _string = read_to_string("file.txt")?; 100 | } 101 | 102 | // `read_to_string` is inferred to be `async` because 103 | // we `.await`ed it. 104 | #[async_std::test] 105 | async fn async_call() { 106 | let _string = read_to_string("file.txt").await?; 107 | } 108 | ``` 109 | 110 | We expect `?async` notation would be most useful for library code which doesn't 111 | do anything particularly specific to async Rust. Think: most of the stdlib, and 112 | ecosystem libraries such as parsers, encoders, and drivers. We expect most 113 | applications to choose to be compiled either as async or non-async, making them 114 | mostly a consumer of `?async` APIs. 115 | 116 | ## A const example 117 | 118 | A main driver of the keywords generics initiative has been our desire to make the 119 | different modifier keywords in Rust feel consistent with one another. Both the 120 | const WG and the async WG were thinking about introducing keyword-traits at the 121 | same time, and we figured we should probably start talking with each other to make 122 | sure that what we were going to introduce felt like it was part of the same 123 | language - and could be extended to support more keywords in the future. 124 | 125 | So with that in mind, it may be unsurprising that for the maybe-`const` trait 126 | bounds and declarations we're going to propose using the `?const` notation. 127 | A common source of confusion with `const fn` is that it actually doesn't 128 | guarantee compile-time execution; it only means that it's *possible* to evaluate 129 | in a `const` compile-time context. So in a way `const fn` has always been a way 130 | of declaring a "maybe-const" function, and there isn't a way to declare an 131 | "always-const" function. More on that later in this post. 132 | 133 | Taking the `Read` example we used earlier, we could imagine a "maybe-const" version 134 | of the `Read` trait to look very similar: 135 | 136 | ```rust 137 | trait ?const Read { 138 | ?const fn read(&mut self, buf: &mut [u8]) -> Result; 139 | ?const fn read_to_string(&mut self, buf: &mut String) -> Result { ... } 140 | } 141 | ``` 142 | 143 | Which we could then use use as a bound in the const `read_to_string` function, 144 | like this: 145 | 146 | ```rust 147 | const fn read_to_string(reader: &mut impl ?const Read) -> std::io::Result { 148 | let mut string = String::new(); 149 | reader.read_to_string(&mut string)?; 150 | Ok(string) 151 | } 152 | ``` 153 | 154 | Just like with `?async` traits, `?const` traits would also need to be labeled as 155 | `?const` when used as a bound. This is important to surface at the trait level, 156 | because it's allowed to pass non-const bounds to maybe-const functions, as long 157 | as no trait methods are called in the function body. This means we need to 158 | distinguish between "never-const" and "maybe-const". 159 | 160 | You may have noticed the `?const` on the trait declaration and the extra 161 | `?const` on the trait methods. This is on purpose: it keeps the path open to 162 | potentially add support for "always-const" or "never-const" methods on traits as 163 | well. In `?async` we know that even if the entire trait is `?async`, some 164 | methods (such as `Iterator::size_hint`) will never be async. And this would 165 | make `?const` and `?async` traits behave similarly using the same rules. 166 | 167 | [read]: https://doc.rust-lang.org/std/io/trait.Read.html 168 | [rts]: https://doc.rust-lang.org/std/io/fn.read_to_string.html 169 | 170 | ## Combining const and async 171 | 172 | We've covered `?async`, and we've covered `?const`. Now what happens if we were 173 | to use them together? Let's take a look at what the `Read` trait would look like 174 | when if we extended it using our designs for `?const` and `?async`: 175 | 176 | ```rust 177 | trait ?const ?async Read { 178 | ?const ?async fn read(&mut self, buf: &mut [u8]) -> Result; 179 | ?const ?async fn read_to_string(&mut self, buf: &mut String) -> Result { .. } 180 | } 181 | 182 | /// Read from a reader into a string. 183 | const ?async fn read_to_string(reader: &mut impl ?const ?async Read) -> io::Result { 184 | let mut string = String::new(); 185 | reader.read_to_string(&mut string).await?; 186 | Ok(string) 187 | } 188 | ``` 189 | 190 | That's sure starting to feel like a lot of keywords, right? We've accurately 191 | described exactly what's going on, but there's a lot of repetition. We know that 192 | if we're dealing with a `const ?async fn`, most arguments probably will 193 | want to be `?const ?async`. But under the syntax rules we've proposed so far, 194 | you'd end up repeating that everywhere. And it probably gets worse once we start 195 | adding in more keywords. Not ideal! 196 | 197 | So we're very eager to make sure that we find a solution to this. And we've been 198 | thinking about a way we could get out of this, which we've been calling 199 | `effect/.do`-notation. This would allow you to mark a function as "generic over 200 | all modifier keywords" by annotating it as `effect fn`, and it would allow the 201 | compiler to insert all the right `.await`, `?`, and `yield` keywords in the 202 | function body by suffixing function calls with `.do`. 203 | 204 | Just to set some expectations: this is the least developed part of our proposal, 205 | and we don't intend to formally propose this until after we're done with some of 206 | the other proposals. But we think it's an important part of the entire vision, 207 | so we wanted to make sure we shared it here. And with that out of the way, 208 | here's the same example we had above, but this time using the `effect/.do`-notation: 209 | 210 | ```rust 211 | trait ?effect Read { 212 | ?effect fn read(&mut self, buf: &mut [u8]) -> Result; 213 | ?effect fn read_to_string(&mut self, buf: &mut String) -> Result { .. } 214 | } 215 | 216 | /// Read from a reader into a string. 217 | ?effect fn read_to_string(reader: &mut impl ?effect Read) -> std::io::Result { 218 | let mut string = String::new(); 219 | reader.read_to_string(&mut string).do; // note the singular `.do` here 220 | string 221 | } 222 | ``` 223 | 224 | One of the things we would like to figure out as part of `effect/.do` is a way 225 | to enable writing conditional effect-bounds. For example: there may be a 226 | function which is always async, may never panic, and is generic over the 227 | remainder of the effects. Or like we're seeing with APIs such as 228 | [`Vec::reserve`] and [`Vec::try_reserve`]: the ability to panic xor return an 229 | error. This will take more time and research to figure out, but we believe it 230 | is something which can be solved. 231 | 232 | [`Vec::reserve`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.reserve 233 | [`Vec::try_reserve`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.try_reserve 234 | 235 | ## Adding support for types 236 | 237 | Something we're keen on doing is not just adding support for `?async` and to 238 | apply to functions, traits, and trait bounds. We would like `?async` to be 239 | possible to use with types as well. This would enable the ecosystem to stop 240 | having to provide both sync and async versions of crates. It would also enable 241 | the stdlib to gradually "asyncify" just like we have been with const. 242 | 243 | The challenge with async types, especially in the stdlib, is that their behavior 244 | will often have to be different when used in async and non-async contexts. At 245 | the very lowest level async system calls work a bit differently from non-async 246 | system calls. But we think we may have a solution for that too in the form of 247 | the `is_async` compiler built-in method. 248 | 249 | Say we wanted to implement `?async File` with a single `?async open` method. The 250 | way we expect this to look will be something like this: 251 | 252 | ```rust 253 | /// A file which may or may not be async 254 | struct ?async File { 255 | file_descriptor: std::os::RawFd, // shared field in all contexts 256 | async waker: Waker, // field only available in async contexts 257 | !async meta: Metadata, // field only available in non-async contexts 258 | } 259 | 260 | impl ?async File { 261 | /// Attempts to open a file in read-only mode. 262 | ?async fn open(path: Path) -> io::Result { 263 | if is_async() { // compiler built-in function 264 | // create an async `File` here; can use `.await` 265 | } else { 266 | // create a non-async `File` here 267 | } 268 | } 269 | } 270 | ``` 271 | 272 | This would enable authors to use different fields depending on whether they're 273 | compiling for async or not, while still sharing a common core. And within 274 | function bodies it would be possible to provide different behaviors depending on 275 | the context as well. The function body notation would work as a generalization 276 | of the currently unstable [`const_eval_select`][eval-select] intrinsic, and at 277 | least for the function bodies we expect a similar `is_const()` compiler built-in 278 | to be made available as well. 279 | 280 | [eval-select]: https://doc.rust-lang.org/std/intrinsics/fn.const_eval_select.html 281 | [connect]: https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.connect 282 | 283 | ## Consistent syntax 284 | 285 | As we alluded to earlier in the post: one of the biggest challenges we see in 286 | language design is adding features in a way that makes them feel like they're in 287 | harmony with the rest of the language - and not something which stands out as 288 | noticably different. And because we're touching on something core to Rust, the 289 | way we do keywords, we have to pay extra close attention here to make sure Rust 290 | keeps feeling like a single language. 291 | 292 | Luckily Rust has the ability to make surface-level changes to the 293 | language through the edition system. There are many things this doesn't let us 294 | do, but it does allow us to require syntax changes. A possibility we're 295 | exploring is leveraging the edition system to make some minor changes to `const` 296 | and `async` so they feel more consistent with one another, and with `?const` and 297 | `?async`. 298 | 299 | For `const` this means there should be a syntactic distinction between `const` 300 | declarations and `const` uses. Like we mentioned earlier in the post, when you 301 | write `const fn` you get a function which can be evaluated both at runtime and 302 | during compilation. But when you write `const FOO: () = ..;` the meaning of 303 | `const` there guarantees compile-time evaluation. One keyword, different 304 | meanings. So for that reason we're wondering whether perhaps it would make more 305 | sense if we changed `const fn` to `?const fn`. This would make it clear that 306 | it's a function which *may* be const-evaluated, but doesn't necessarily have to - 307 | and can also be called from non-`const` contexts. 308 | 309 | ```rust 310 | //! Define a function which may be evaluated both at runtime and during 311 | //! compilation. 312 | 313 | // Current 314 | const fn meow() -> String { .. } 315 | 316 | // Proposed 317 | ?const fn meow() -> String { .. } 318 | ``` 319 | 320 | For `async` we're considering some similar surface-level changes. The Async WG 321 | is in the process of expanding the "async functions in traits" design into an 322 | design covering "async traits" entirely, largely motivated by the desire to be 323 | able to add `+ Send` bound to anonymous futures. There are more details about 324 | this in ["Lightweight, Predictable Async Send Bounds"][bounds-post] by Eric 325 | Holk. But it would roughly become the following notation: 326 | 327 | [bounds-post]: https://blog.theincredibleholk.org/blog/2023/02/16/lightweight-predictable-async-send-bounds/ 328 | 329 | ```rust 330 | struct File { .. } 331 | impl async Read for File { // async trait declaration 332 | async fn read(&mut self, buf: &mut [u8]) -> io::Result { .. } // async method 333 | } 334 | 335 | async fn read_to_string(reader: &mut impl async Read) -> io::Result { // async trait bound 336 | let mut string = String::new(); 337 | reader.read_to_string(&mut string).await?; 338 | Ok(string) 339 | } 340 | ``` 341 | 342 | This would make `impl ?async Read` and `impl async Read` consistent with each 343 | other. And it would open the door for `trait ?async` traits to be passed to 344 | `impl async Read` and be guaranteed to be always interpreted as `trait async`. 345 | Which is another nice consistency gain. 346 | 347 | The final thing we're looking at is `async`-notation for types. To implement 348 | inherent `?async` methods on types, our current design requires the type to also 349 | be marked as `?async`. In order to bring `?async` and `async` closer together, 350 | we're exploring whether it might also make sense to require types to be marked 351 | as `async` as well: 352 | 353 | ```rust 354 | //! Proposed: define a method on a maybe-async type 355 | struct ?async File { .. } 356 | impl ?async File { 357 | ?async fn open(path: PathBuf) -> io::Result { .. } 358 | } 359 | 360 | //! Current: define a method on an always-async type 361 | struct File { .. } 362 | impl File { 363 | async fn open(path: PathBuf) -> io::Result { .. } 364 | } 365 | 366 | //! Proposed: define a method on an always-async type 367 | struct async File { .. } 368 | impl async File { 369 | async fn open(path: PathBuf) -> io::Result { .. } 370 | } 371 | ``` 372 | 373 | We already have something similar going on for "always-const" arguments via the 374 | const-generics system. These look something like this: 375 | 376 | ```rust 377 | fn foo() {} 378 | ``` 379 | 380 | Every "always-const" argument to the function must always be marked by `const`, 381 | so it wouldn't be entirely without precedent for every "always-async" type to 382 | always require to be marked using `async`. So we're exploring some of what might 383 | be possible here. 384 | 385 | ## The tentative plan 386 | 387 | We plan to initially focus our efforts on the `async` and `const` keywords only. 388 | We're feeling ready to start converting some of our designs into RFCs, and start 389 | putting them out for review. In the coming months we expect to start writing 390 | the following proposals (in no particular order): 391 | 392 | - `?async fn` notation without trait bounds, including an `is_async` mechanism. 393 | - `trait async` declarations and bounds. 394 | - `trait ?async` declarations and bounds, `trait ?const` declarations and bounds. 395 | - `?const fn` notation without trait bounds. 396 | - `struct async` notation and `struct ?const` notation. 397 | 398 | We'll be working closely with the Lang Team, Const WG, and Async WG on these 399 | proposals, and in some cases (such as `trait async`) we may even take an 400 | advising role with a WG directly driving the RFC. As usual, these will be going 401 | through the RFC-nightly-stabilization cycle. And only once we're fully confident 402 | in them will they become available on stable Rust. 403 | 404 | We're intentionally not yet including `effect/.do` notation on this roadmap. We 405 | expect to only be able to start this work once we have `?async` on nightly, 406 | which we don't yet have. So for now we'll continue work on designing it within 407 | the initiative, and hold off on making plans to introduce it quiet yet. 408 | 409 | ## Conclusion 410 | 411 | And that concludes the 9-month progress report of the Keyword Generics 412 | Initiative. We hope to be able to provide more exact details about things such 413 | as desugarings, semantics, and alternatives in the RFCs. We're pretty stoked with the 414 | progress we've made in these past few months! Something which I don't think 415 | we've mentioned yet, but is probably good to share: we've actually prototyped 416 | much of the work in this post already; so we're feeling fairly confident all of 417 | this may actually *actually* work. And that is something we're 418 | incredibly excited for! 419 | -------------------------------------------------------------------------------- /updates/2022-07-27-announcing-the-keyword-generics-initiative.md: -------------------------------------------------------------------------------- 1 | # Announcing the Keyword Generics Initiative" 2 | 3 | _This post was originally posted on the [Inside Rust 4 | Blog](https://blog.rust-lang.org/inside-rust/2022/07/27/keyword-generics.html), 5 | but is included in this repository to be more easily referenced._ 6 | 7 | We ([Oli], [Niko], and [Yosh]) are excited to announce the start of the [Keyword 8 | Generics Initiative][kwi], a new initiative [^initiative] under the purview of 9 | the language team. We're officially just a few weeks old now, and in this post 10 | we want to briefly share why we've started this initiative, and share some 11 | insight on what we're about. 12 | 13 | [Oli]: https://github.com/oli-obk 14 | [Niko]: https://github.com/nikomatsakis 15 | [Yosh]: https://github.com/yoshuawuyts 16 | [kwi]: https://github.com/rust-lang/keyword-generics-initiative 17 | 18 | [^initiative]: Rust governance terminology can sometimes get confusing. An 19 | "initiative" in Rust parlance is different from a "working group" or "team". 20 | Initiatives are intentionally limited: they exist to explore, design, and 21 | implement specific pieces of work - and once that work comes to a close, the 22 | initiative will wind back down. This is different from, say, the lang team - 23 | which essentially carries a `'static` lifetime - and whose work does 24 | not have a clearly defined end. 25 | 26 | ## A missing kind of generic 27 | 28 | One of Rust's defining features is the ability to write functions which are 29 | _generic_ over their input types. That allows us to write a function once, 30 | leaving it up to the compiler to generate the right implementations for us. 31 | 32 | Rust allows you to be generic over types - it does not allow you to be generic 33 | over other things that are usually specified by keywords. For example, whether a 34 | function is async, whether a function can fail or not, whether a function is 35 | const or not, etc. 36 | 37 | The post ["What color is your function"][color] [^color] describes what happens 38 | when a language introduces async functions, but with no way to be generic over 39 | them: 40 | 41 | > I will take async-await over bare callbacks or futures any day of the week. 42 | > But we’re lying to ourselves if we think all of our troubles are gone. As soon 43 | > as you start trying to write higher-order functions, or reuse code, you’re 44 | > right back to realizing color is still there, bleeding all over your codebase. 45 | 46 | This isn't just limited to async though, it applies to all modifier keywords - 47 | including ones we may define in the future. So we're looking to fill that gap 48 | by exploring something we call "keyword generics" [^name]: the ability to be 49 | generic over keywords such as `const` and `async`. 50 | 51 | [color]: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ 52 | [^color]: R. Nystrom, “What Color is Your Function?,” Feb. 01, 2015. 53 | https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ 54 | (accessed Apr. 06, 2022). 55 | 56 | [^name]: The longer, more specific name would be: "keyword modifier generics". 57 | We've tried calling it that, but it's a bit of a mouthful. So we're just 58 | sticking with "keyword generics" for now, even if the name for this feature may 59 | end up being called something more specific in the reference and documentation. 60 | 61 | To give you a quick taste of what we're working on, this is roughly how we 62 | imagine you may be able to write a function which is generic over "asyncness" 63 | in the future: 64 | 65 | > Please note that this syntax is entirely made up, just so we can use something 66 | > in examples. Before we can work on syntax we need to finalize the semantics, 67 | > and we're not there yet. This means the syntax will likely be subject to 68 | > change over time. 69 | 70 | ```rust 71 | async trait Read { 72 | async fn read(&mut self, buf: &mut [u8]) -> Result; 73 | async fn read_to_string(&mut self, buf: &mut String) -> Result { ... } 74 | } 75 | 76 | /// Read from a reader into a string. 77 | async fn read_to_string(reader: &mut impl Read * A) -> std::io::Result { 78 | let mut string = String::new(); 79 | reader.read_to_string(&mut string).await?; 80 | string 81 | } 82 | ``` 83 | 84 | This function introduces a "keyword generic" parameter into the function of `A`. 85 | You can think of this as a flag which indicates whether the function is being 86 | compiled in an async context or not. The parameter `A` is forwarded to the `impl 87 | Read`, making that conditional on "asyncness" as well. 88 | 89 | In the function body you can see a `.await` call. Because [the `.await` keyword 90 | marks cancellation sites][cancel] we unfortunately can't just infer them 91 | [^cancellation]. Instead we require them to be written for when the code is 92 | compiled in async mode, but are essentially reduced to a no-op in non-async 93 | mode. 94 | 95 | [cancel]: https://blog.yoshuawuyts.com/async-cancellation-1/ 96 | [^cancellation]: No really, we can't just infer them - and it may not be as 97 | simple as omitting all `.await` calls either. The Async WG is working through 98 | the full spectrum of cancellation sites, async drop, and more. But for now we're 99 | working under the assumption that `.await` will remain relevant going forward. 100 | And even in the off chance that it isn't, fallibility has similar requirements 101 | at the call site as async does. 102 | 103 | We still have lots of details left to figure out, but we hope this at least 104 | shows the general *feel* of what we're going for. 105 | 106 | ## A peek at the past: horrors before const 107 | 108 | Rust didn't always have `const fn` as part of the language. A long long long long 109 | long time ago (2018) we had to write a regular function for runtime computations 110 | and associated const of generic type logic for compile-time computations. As an 111 | example, to add the number `1` to a constant provided to you, you had to write 112 | ([playground]): 113 | 114 | [playground]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=50e818b79b8af322ed4384d3c33e9773 115 | 116 | ```rust 117 | trait Const { 118 | const VAL: T; 119 | } 120 | 121 | /// `42` as a "const" (type) generic: 122 | struct FourtyTwo; 123 | impl Const for FourtyTwo { 124 | const VAL: i32 = 42; 125 | } 126 | 127 | /// `C` -> `C + 1` operation: 128 | struct AddOne>(C); 129 | impl> Const for AddOne { 130 | const VAL: i32 = C::VAL + 1; 131 | } 132 | 133 | AddOne::::VAL 134 | ``` 135 | 136 | Today this is as easy as writing a `const fn`: 137 | 138 | ```rust 139 | const fn add_one(i: i32) -> i32 { 140 | i + 1 141 | } 142 | 143 | add_one(42) 144 | ``` 145 | 146 | The interesting part here is that you can also just call this function in 147 | runtime code, which means the implementation is shared between both `const` 148 | (CTFE[^ctfe]) and non-`const` (runtime) contexts. 149 | 150 | [^ctfe]: CTFE stands for "Compile Time Function Execution": `const` functions 151 | can be evaluated during compilation, which is implemented using a Rust 152 | interpreter (miri). 153 | 154 | ## Memories of the present: async today 155 | 156 | People write duplicate code for async/non-async with the only difference being 157 | the `async` keyword. A good example of that code today is [`async-std`], which 158 | duplicates and translates a large part of the stdlib's API surface to be async 159 | [^async-std]. And because the Async WG has made it an explicit goal to [bring 160 | async Rust up to par with non-async Rust][async-vision], the issue of code 161 | duplication is particularly relevant for the Async WG as well. Nobody on the 162 | Async WG seems particularly keen on proposing we add a second instance of just 163 | about every API currently in the stdlib. 164 | 165 | [`async-std`]: https://docs.rs/async-std/latest/async_std/ 166 | [async-vision]: https://rust-lang.github.io/wg-async/vision/how_it_feels.html 167 | [^async-std]: Some limitations in `async-std` apply: async Rust is missing async 168 | `Drop`, async traits, and async closures. So not all APIs could be duplicated. 169 | Also `async-std` explicitly didn't reimplement any of the collection APIs to be 170 | async-aware, which means users are subject to the "sandwich problem". The 171 | purpose of `async-std` was to be a proving ground to test whether creating 172 | an async mirror of the stdlib would be possible: and it's proven that it is, as 173 | far as was possible with missing language features. 174 | 175 | We're in a similar situation with `async` today as `const` was prior to 2018. 176 | Duplicating entire interfaces and wrapping them in `block_on` calls is the 177 | approach taken by e.g. the `mongodb` 178 | [[async](https://docs.rs/mongodb/latest/mongodb/index.html), 179 | [non-async](https://docs.rs/mongodb/latest/mongodb/sync/index.html)], `postgres` 180 | [[async](https://docs.rs/tokio-postgres/latest/tokio_postgres/index.html), 181 | [non-async](https://docs.rs/postgres/latest/postgres/)], and `reqwest` 182 | [[async](https://docs.rs/reqwest/latest/reqwest/), 183 | [non-async](https://docs.rs/reqwest/latest/reqwest/blocking/index.html)] crates: 184 | ```rust 185 | // Async functionality like this would typically be exposed from a crate "foo": 186 | async fn bar() -> Bar { 187 | // async implementation goes here 188 | } 189 | ``` 190 | 191 | ```rust 192 | // And a sync counterpart would typically be exposed from a crate 193 | // named "blocking_foo" or a submodule on the original crate as 194 | // "foo::blocking". This wraps the async code in a `block_on` call: 195 | fn bar() -> Bar { 196 | futures::executor::block_on(foo::bar()) 197 | } 198 | ``` 199 | 200 | This situation is not ideal. Instead of using the host's synchronous syscalls, 201 | we're now going through an async runtime to get the same results - something 202 | which is often not zero-cost. But more importantly, it's rather hard to 203 | keep both a sync and async API version of the same crate in, err, sync with each 204 | other. Without automation it's really easy for the two APIs to get out of sync, 205 | leading to mismatched functionality. 206 | 207 | The ecosystem has come up with some solutions to this, perhaps most notably the 208 | proc-macro based [`maybe-async` crate][maybe-async]. Instead of writing two 209 | separate copies of `foo`, it generates a sync and async variant for you: 210 | 211 | ```rust 212 | #[maybe_async] 213 | async fn foo() -> Bar { ... } 214 | ``` 215 | 216 | [maybe-async]: https://docs.rs/maybe-async/0.2.6/maybe_async/ 217 | 218 | While being useful, the macro has clear limitations with respect to diagnostics 219 | and ergonomics. That's absolutely not an issue with the crate, but an inherent 220 | property of the problem it's trying to solve. Implementing a way to be generic 221 | over the `async` keyword is something which will affect the language in many 222 | ways, and a type system + compiler will be better equipped to handle it than 223 | proc macros reasonably can. 224 | 225 | ## A taste of trouble: the sandwich problem 226 | 227 | A pervasive issue in existing Rust is the _sandwich_ problem. It occurs when a 228 | type passed into an operation wants to perform control flow not supported by the 229 | type it's passed into. Thus creating a _sandwich_ [^dilemma] The classic example 230 | is a `map` operation: 231 | 232 | [^dilemma]: Not to be confused with the higher-order _sandwich dilemma_ which is 233 | when you look at the sandwich problem and attempt to determine whether the 234 | sandwich is two slices of bread with a topping in between, or two toppings with 235 | a slice of bread in between. Imo the operation part of the problem feels more 236 | _bready_, but that would make for a weird-looking sandwich. Ergo: sandwich 237 | dilemma. (yes, you can ignore all of this.) 238 | 239 | ```rust 240 | enum Option { 241 | Some(T), 242 | None, 243 | } 244 | 245 | impl Option { 246 | fn map(self, f: impl FnOnce(T) -> J) -> Option { ... } 247 | } 248 | 249 | my_option.map(|x| x.await) 250 | ``` 251 | 252 | This will produce a compiler error: the closure `f` is not an async context, so 253 | `.await` cannot be used within it. And we can't just convert the closure to be 254 | `async` either, since `fn map` doesn't know how to call async functions. In 255 | order to solve this issue, we could provide a new `async_map` method which 256 | _does_ provide an async closure. But we may want to repeat those for more 257 | effects, and that would result in a combinatorial explosion of effects. Take for 258 | example "can fail" and "can be async": 259 | 260 | | | not async | async | 261 | | -------------- | ------------ | ------------------ | 262 | | __infallible__ | `fn map` | `fn async_map` | 263 | | __fallible__ | `fn try_map` | `fn async_try_map` | 264 | 265 | That's a lot of API surface for just a single method, and __that problem 266 | multiplies across the entire API surface in the stdlib__. We expect that once we 267 | start applying "keyword generics" to traits, we will be able to solve the 268 | sandwich problem. The type `f` would be marked generic over a set of effects, 269 | and the compiler would choose the right variant during compilation. 270 | 271 | ## Affecting all effects 272 | 273 | Both `const` and `async` share a very similar issue, and we expect that other 274 | "effects" will face the same issue. "fallibility" is particularly on our mind here, 275 | but it isn't the only effect. In order for the language to feel consistent we 276 | need consistent solutions. 277 | 278 | ## FAQ 279 | 280 | ### Q: Is there an RFC available to read? 281 | 282 | Rust initiatives are intended for _exploration_. The announcement of the Keyword 283 | Generics Initiative marks the _start_ of the exploration process. Part of 284 | exploring is not knowing what the outcomes will be. Right now we're in the 285 | "pre-RFC" phase of design. What we hope we'll achieve is to enumerate the 286 | full problem space, design space, find a balanced solution, and eventually 287 | summarize that in the form of an RFC. Then after the RFC is accepted: implement 288 | it on nightly, work out the kinks, and eventually move to stabilize. But we may 289 | at any point during this process conclude that this initiative is actually 290 | infeasible and start ramping down. 291 | 292 | But while we can't make any _assurances_ about the outcome of the initiative, 293 | what we can share is that we're pretty optimistic about the initiative overall. 294 | We wouldn't be investing the time we are on this if we didn't think we'd be 295 | actually be able to see it through to completion. 296 | 297 | ### Q: Will this make the language more complicated? 298 | 299 | The goal of keyword generics is not to minimize the complexity of the Rust 300 | programming language, but to _minimize the complexity of programming in Rust._ 301 | These two might sound similar, but they're not. Our reasoning here is that by 302 | _adding_ a feature, we will actually be able to significantly reduce the surface 303 | area of the stdlib, crates.io libraries, and user code - leading to a more 304 | streamlined user experience. 305 | 306 | Choosing between sync or async code is a fundamental choice which needs to be 307 | made. This is complexity which cannot be avoided, and which needs to exist 308 | somewhere. Currently in Rust that complexity is thrust entirely on users of 309 | Rust, making them responsible for choosing whether their code should support 310 | async Rust or not. But other languages have made diferent choices. For example 311 | Go doesn't distinguish between "sync" and "async" code, and has a runtime which 312 | is able to remove that distinction. 313 | 314 | In today's Rust application authors choose whether their application will be sync 315 | or async, and even after the introduction of keyword generics we don't really 316 | expect that to change. All generics eventually need to have their types known, 317 | and keyword generics are no different. What we're targeting is the choice made 318 | by _library_ authors whether their library supports is sync or async. With 319 | keyword generics library authors will be able to support both with the help of 320 | the compiler, and leave it up to application authors to decide how they want to 321 | compile their code. 322 | 323 | ### Q: Are you building an effect system? 324 | 325 | The short answer is: kind of, but not really. "Effect systems" or "algebraic 326 | effect systems" generally have a lot of surface area. A common example of what 327 | effects allow you to do is implement your own `try/catch` mechanism. What we're 328 | working on is intentionally limited to built-in keywords only, and wouldn't 329 | allow you to implement anything like that at all. 330 | 331 | What we do share with effect systems is that we're integrating modifier keywords 332 | more directly into the type system. Modifier keywords like `async` are often 333 | referred to as "effects", so being able to be conditional over them in 334 | composable ways effectively gives us an "effect algebra". But that's very 335 | different from "generalized effect systems" in other languages. 336 | 337 | ### Q: Are you looking at other keywords beyond `async` and `const`? 338 | 339 | For a while we were referring to the initiative as "modifier generics" or 340 | "modifier keyword generics", but it never really stuck. We're only really 341 | interested in keywords which modify how types work. Right now this is `const` 342 | and `async` because that's what's most relevant for the const-generics WG and 343 | async WG. But we're designing the feature with other keywords in mind as well. 344 | 345 | The one most at the top of our mind is a future keyword for fallibility. There 346 | is talk about introducing `try fn() {}` or `fn () -> throws` syntax. This could 347 | make it so methods such as `Iterator::filter` would be able to use `?` to break 348 | out of the closure and short-circuit iteration. 349 | 350 | Our main motivation for this feature is that without it, it's easy for Rust to 351 | start to feel _disjointed_. We sometimes joke that Rust is actually 3-5 352 | languages in a trenchcoat. Between const rust, fallible rust, async rust, unsafe 353 | rust - it can be easy for common APIs to only be available in one variant of the 354 | language, but not in others. We hope that with this feature we can start to 355 | systematically close those gaps, leading to a more consistent Rust experience 356 | for _all_ Rust users. 357 | 358 | ### Q: What will the backwards compatibility story be like? 359 | 360 | Rust has pretty strict backwards-compatibility guarantees, and any feature we 361 | implement needs to adhere to this. Luckily we have some wiggle room because of 362 | the edition mechanism, but our goal is to shoot for maximal backwards compat. We 363 | have some ideas of how we're going to make this work though, and we're 364 | cautiously optimistic we might actually be able to pull this off. 365 | 366 | But to be frank: this is by far one of the hardest aspects of this feature, and 367 | we're lucky that we're not designing any of this just by ourselves, but have the 368 | support of the language team as well. 369 | 370 | ### Q: Aren't implementations sometimes fundamentally different? 371 | 372 | Const Rust can't make any assumptions about the host it runs on, so it can't do 373 | anything platform-specific. This includes using more efficient instructions of 374 | system calls which are only available in one platform but not another. In order 375 | to work around this, the [`const_eval_select`][ces] intrinsic in the standard 376 | library enables `const` code to detect whether it's executing during CTFE or 377 | runtime, and execute different code based on that. 378 | 379 | [ces]: https://doc.rust-lang.org/std/intrinsics/fn.const_eval_select.html 380 | 381 | For async we expect to be able to add a similar intrinsic, allowing library 382 | authors to detect whether code is being compiled as sync or async, and do 383 | something different based on that. This includes: using internal concurrency, or 384 | switching to a different set of system calls. We're not sure whether an 385 | intrinsic is the right choice for this though; we may want to provide a more 386 | ergonomic API for this instead. But because keyword generics is being designed 387 | as a consistent feature, we expect that whatever we end up going with can be used 388 | consistently by _all_ modifier keywords. 389 | 390 | ## Conclusion 391 | 392 | In this post we've introduced the new keyword generics initiative, explained why 393 | it exists, and shown a brief example of what it might look like in the future. 394 | 395 | The initiative is active on the Rust Zulip under 396 | [`t-lang/keyword-generics`][zulip] - if this seems interesting to you, please 397 | pop by! 398 | 399 | _Thanks to everyone who's helped review this post, but in particular: 400 | [fee1-dead][fd], [Daniel Henry-Mantilla][dhm], and [Ryan Levick][rylev]_ 401 | 402 | [zulip]: https://rust-lang.zulipchat.com/#narrow/stream/328082-t-lang.2Fkeyword-generics 403 | [fd]: https://github.com/fee1-dead 404 | [dhm]: https://github.com/danielhenrymantilla 405 | [rylev]: https://github.com/rylev 406 | -------------------------------------------------------------------------------- /explainer/effect-generic-trait-declarations.md: -------------------------------------------------------------------------------- 1 | - Feature Name: `effect-generic-trait-decls` 2 | - Start Date: (2024-01-01) 3 | - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) 4 | - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) 5 | 6 | # Summary 7 | [summary]: #summary 8 | 9 | This RFC introduces Effect-Generic Trait Declarations. These are traits which 10 | are generic over Rust's built-in effect keywords such as `async`. 11 | Instead of defining two near-identical traits per effect, this RFC allows a 12 | single trait to be declared which is generic over the effect. Here is a variant 13 | of the `Into` trait which can be implemented as either async or not. 14 | 15 | ```rust 16 | #[maybe(async)] 17 | trait Into: Sized { 18 | #[maybe(async)] 19 | fn into(self) -> T; 20 | } 21 | ``` 22 | 23 | Implementers can then choose whether to implement the base version or the 24 | effectful version of the trait. If they want the base version they don't include 25 | the `async` effect. If they want the `async` version they can include the 26 | `async` keyword. 27 | 28 | ```rust 29 | /// The base implementation 30 | impl Into for Cat { // The trait is not marked async… 31 | fn into(self) -> Loaf { // and thus neither is the method. 32 | self.nap() 33 | } 34 | } 35 | 36 | /// The async implementation 37 | impl async Into for AsyncCat { // The trait is marked async… 38 | async fn into(self) -> AsyncLoaf { // and thus so is the method. 39 | self.async_nap().await 40 | } 41 | } 42 | ``` 43 | 44 | # Motivation 45 | [motivation]: #motivation 46 | 47 | Rust is a single language that's made up of several different sub-languages. 48 | There are the macro languages, as well as the generics language, patterns, 49 | const, unsafe, and async sub-languages. Rust works anywhere from a 50 | micro-controller to Windows, and even browsers. One of the biggest challenges we 51 | have is to not only keep the language as easy to use as we can, it's to ensure 52 | it works relatively consistently on all the different platforms we support. 53 | 54 | We're currently in the process of adding support for the `const` and `async` 55 | language features to Rust. But we're looking at various other extensions as 56 | well, such as generator functions, fallible functions, linearity, and more. 57 | These are really big extensions to the language, whose implementation will take 58 | on the order of years. If we want to successfully introduce these features, 59 | they'll need to be integrated with every other part of the language. As well as 60 | having wide support in the stdlib. 61 | 62 | Effect Generic Trait Declarations are a minimal language feature which enable 63 | traits to add support for new effects, without needing to duplicate the trait 64 | itself. So rather than having a trait `Into`, `TryInto`, `AsyncInto`, and the 65 | inevitable `TryAsyncInto` - we would declare a single trait `Into` once, which 66 | has support for any combination of `async` and `try` effects. This is 67 | backwards-compatible by design, and should be able to support any number of 68 | effect extensions we come up with in the future. Ensuring the language can keep 69 | evolving to our needs. 70 | 71 | ## Guaranteeing API consistency 72 | 73 | Evolving a programming language and stdlib is pretty difficult. We have to pay 74 | close attention to details. And in Rust specifically: once we make a mistake 75 | it's pretty hard to roll back. And we've made mistakes with effects in the past, 76 | which we now have to work with [^1]. 77 | 78 | [^1]: In Rust 1.34 we stabilized a new trait: `TryInto`. This was supposed to be 79 | the fallible version of the `Into` trait, containing a new associated type 80 | `Error`. However since Rust 1.0 we've also had the 81 | [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) trait, which 82 | *also* provides a fallible conversion, but has an associated type `Err`. This 83 | means that when writing a fallible conversion trait, it's unclear whether the 84 | associated type should be called `Err` or `Error`. 85 | 86 | This might seem minor, but without automation these subtle 87 | similar-but-not-quite-the-same kinds of differences stand out. The only way to 88 | ensure that different APIs in different contexts work consistently is via 89 | automation. And the best automation we have for this is the type system. 90 | 91 | # Guide-level explanation 92 | [guide-level-explanation]: #guide-level-explanation 93 | 94 | ## Trait definitions 95 | 96 | The base of Effect Generic Trait Declarations is the ability to declare traits 97 | as being generic over effects. This RFC currently only considers the `async` 98 | effect, but should be applicable to most other effects (modulo `unsafe` and 99 | `const`, more on that later). The way a trait is defined is by adding a 100 | `#[maybe(effect)]` notation. This signals that a trait may be implemented as 101 | carrying the effect. For example, a version of the `Read` trait which may or 102 | may not be `async` would be defined as: 103 | 104 | ```rust 105 | #[maybe(async)] 106 | pub trait Read { 107 | #[maybe(async)] 108 | fn read(&mut self, buf: &mut [u8]) -> Result; 109 | } 110 | ``` 111 | 112 | ## Trait implementations 113 | 114 | Traits can be implemented as either async or non-async. The trait-level 115 | `#[maybe(async)]` can be thought of as a const-generic bool which determines the 116 | value of the method-level `#[maybe(async)]` declarations. So if a trait is 117 | implemented as `async`, all methods tagged as `#[maybe(async)]` have to be async 118 | too. 119 | 120 | ```rust 121 | /// The base implementation 122 | impl Read for Reader { 123 | fn read(&mut self, buf: &mut [u8]) -> Result { 124 | // ... 125 | } 126 | } 127 | 128 | /// The async implementation 129 | impl async Read for AsyncReader { 130 | async fn read(&mut self, buf: &mut [u8]) -> Result { 131 | // ... 132 | } 133 | } 134 | ``` 135 | 136 | ## Method markers 137 | 138 | This RFC only covers trait methods which carry an effect or not. It does not 139 | cover types which may or may not have effects. The intent is to add this via a 140 | future extension, so for the scope of this RFC we have to be able to declare 141 | certain methods as not being generic over effects. This is the default behavior; 142 | no extra annotations are needed for this. 143 | 144 | Taking the `Read` trait example again; the `chain` method returns a type `Chain` 145 | which implements `Iterator`. Accounting for the `chain` method, the declaration 146 | of `Read` would be: 147 | 148 | ```rust 149 | #[maybe(async)] 150 | pub trait Read { 151 | ... 152 | 153 | // This method is not available for `impl async Read` 154 | fn chain(self, next: R) -> Chain 155 | where Self: Sized { .. } 156 | } 157 | ``` 158 | 159 | Because `chain` is not marked as `maybe(async)`, when implementing `async Read`, 160 | it will not be available. If a synchronous method has to be available in an 161 | async context, it should be possible to mark it as `not(async)`, so that it's 162 | clear it's part of the API contract for the async implementation - and is never 163 | async. 164 | 165 | ```rust 166 | #[maybe(async)] 167 | pub trait Read { 168 | ... 169 | 170 | // This method would be available for `impl async Read` 171 | #[not(async)] 172 | fn chain(self, next: R) -> Chain 173 | where Self: Sized { .. } 174 | } 175 | ``` 176 | 177 | # Reference-level explanation 178 | [reference-level-explanation]: #reference-level-explanation 179 | 180 | ## Effect lowering 181 | 182 | At the MIR level the lowering of `#[maybe(effect)]` is shared with `const`, and 183 | is essentially implemented via const generic bools. Take the following maybe-async 184 | definition of `Into`: 185 | 186 | ```rust 187 | // Trait definition 188 | #[maybe(async)] 189 | trait Into: Sized { 190 | #[maybe(async)] 191 | fn into(self) -> T; 192 | } 193 | ``` 194 | 195 | At the type level the `#[maybe(async)]` system is lowered to a const bool which 196 | determines whether the function should be async. If the trait is implemented as 197 | async, the bool is set to true. If it isn't, it's set to false. 198 | 199 | ```rust 200 | // Lowered trait definition 201 | trait Into: Sized { 202 | type Ret = T; 203 | fn into(self) -> Self::Ret; 204 | } 205 | ``` 206 | 207 | By default the const bool is set to false. The return type of the 208 | function here is the base return type of the definition: 209 | 210 | ```rust 211 | /// The base implementation 212 | impl Into for Cat { 213 | fn into(self) -> Loaf { 214 | self.nap() 215 | } 216 | } 217 | 218 | // Lowered base trait impl 219 | impl Into for Cat { // IS_ASYNC = false 220 | type Ret = T; 221 | fn into(self) -> Self::Ret { 222 | self.nap() 223 | } 224 | } 225 | ``` 226 | 227 | However if we implement the async version of the trait things change a little. 228 | In the lowering the const bool is set to `true` to indicate we are in fact 229 | async. And in the lowering we wrap the return type in an `impl Future`, as well 230 | as return an anonymous `async {}` block from the function. 231 | 232 | ```rust 233 | /// The async implementation 234 | impl async Into for AsyncCat { 235 | async fn into(self) -> AsyncLoaf { 236 | self.async_nap().await 237 | } 238 | } 239 | 240 | // Lowered async trait impl 241 | impl Into for AsyncCat { // IS_ASYNC = true 242 | type Ret = impl Future; 243 | fn into(self) -> Self::Ret { 244 | async move { 245 | self.async_nap().await 246 | } 247 | } 248 | } 249 | ``` 250 | 251 | ## Effect lowering with lifetimes 252 | 253 | Things become more interesting when lifetimes are involved in the effectful 254 | lowering of a trait. The return type of an `async fn` which takes a reference 255 | has to be a future with a lifetime. Which means it's in our lowering our 256 | associated type can't be a plain future - it has to be a future with a lifetime 257 | attached. And this requires lifetime GATs to work. 258 | 259 | Say instead of the async version of `Into`, we tried to write the maybe-async 260 | version of [`AsRef`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) 261 | [^asref]. We could define it as follows: 262 | 263 | [^asref]: this is just for the purpose of an example; I don't actually know of 264 | any cases which want an async version of `AsRef`. But never say never. 265 | 266 | ```rust 267 | /// The trait definition 268 | #[maybe(async)] 269 | pub trait AsRef 270 | where 271 | T: ?Sized, 272 | { 273 | #[maybe(async)] 274 | fn as_ref(&self) -> &T; 275 | } 276 | 277 | /// The lowering of the trait definition 278 | pub trait AsRef 279 | where 280 | T: ?Sized, 281 | { 282 | type Ret<'a> = &'a T 283 | where Self: 'a; 284 | fn as_ref(&self) -> Self::Ret<'_>; 285 | } 286 | ``` 287 | 288 | We could then implement it like we did with our `Into` impl. The non-async impl 289 | would look like this: 290 | 291 | ```rust 292 | /// The base implementation 293 | impl AsRef for Cat { 294 | fn as_ref(&self) -> &Loaf { 295 | self.nap_ref() 296 | } 297 | } 298 | 299 | /// Lowering of the base implementation 300 | impl AsRef for Cat { // IS_ASYNC = false 301 | type Ret<'a> = &'a Loaf 302 | where Self: 'a; 303 | fn as_ref(&self) -> Self::Ret<'_> { 304 | self.nap_ref() 305 | } 306 | } 307 | ``` 308 | 309 | And the async implementation would look like this: 310 | 311 | ```rust 312 | /// The base implementation 313 | impl async AsRef for AsyncCat { 314 | async fn as_ref(&self) -> &Loaf { 315 | self.async_nap_ref().await 316 | } 317 | } 318 | 319 | /// Lowering of the base implementation 320 | impl AsRef for AsyncCat { // IS_ASYNC = true 321 | type Ret<'a> = impl Future + 'a 322 | where Self: 'a; 323 | fn as_ref(&self) -> Self::Ret<'_> { 324 | async { 325 | self.async_nap_ref().await 326 | } 327 | } 328 | } 329 | ``` 330 | 331 | While effect-generic trait definitions with lifetimes do rely on GATs in their 332 | lowering, crucially they don't rely on any potential notion of lifetime-generics to 333 | function. The right lifetime GATs can be emitted by the compiler during 334 | lowering, and should therefor always be accurate. 335 | 336 | ## Effect states 337 | [effect states]: #effect-states 338 | 339 | This RFC reasons about effects as being in one of four logical states: 340 | 341 | - __Always:__ This is when an effect is always present. For example: if a 342 | function implements some kind of concurrency operations, it may always want to 343 | be async. This is signaled by the existing meaning of the `async fn` notation. 344 | - __Maybe__: This is when an effect may sometimes be present. This will 345 | apply to most traits in the stdlib. For example, if we want to write an async 346 | version of the `Read` trait its associated methods will also want to be `async`. 347 | - __Not__: This is when an effect is never present. For example: 348 | `Iterator::size_hint` will likely *never* want to be async, even if the trait 349 | and most methods are async. In order for methods to be available in the 350 | effectful implementatin of the trait, they have to be marked as never 351 | carrying the effect. 352 | - __Unknown:__ Methods which haven't explicitly declared which logical state 353 | they're in are *unknown*. This is a distinct state from *not*, because a method 354 | may be converted from *unknown* to *maybe* without breaking backwards 355 | compatibility. 356 | 357 | For the `async` effect methods which are always async are labeled `async fn`. 358 | Methods which may or may not be async are labeled `#[maybe(async)]`. Methods 359 | which are never async are labeled `#[not(async)]`. All other methods are 360 | unlabeled, and are not made available to the async implementation of the trait. 361 | 362 | ## Concrete impls and coherence 363 | 364 | With the eye on forward-compatibility, and a potential future where types can 365 | themselves also be generic over effects, for now types may only implement either 366 | the effectful or the base variant of the trait. This ensures that the door is 367 | kept open for effect generic implementations later on. As well as ensures that 368 | during trait selection the trait variant remains unambiguous. The diagnostics 369 | for this case should clearly communicate that only a single trait variant can be 370 | implemented per type. 371 | 372 | ```text 373 | error[E0119]: conflicting implementations of trait `Into` for type `Cat` 374 | --> src/lib.rs:5:1 375 | | 376 | 4 | impl Into for Cat {} 377 | | ----------------- first implementation here 378 | 5 | impl async Into for Cat {} 379 | | ^^^^^^^^^^^^^^^^^ conflicting implementation for `Cat` 380 | | 381 | | help: types can't both implement the sync and async variant of a trait 382 | ``` 383 | 384 | ## Trait bounds 385 | 386 | Using effect generic trait definitions in trait bounds should be no problem, 387 | assuming the bounds are concrete. Unlike concrete types, generic bounds may 388 | implement both effecful and uneffectful implementations for the same bounds as 389 | long as they target non-overlapping sets of traits. For example, assuming we had 390 | a maybe-async version of `Into`, introducing a maybe-async version of `From` 391 | would allow us to write the following non-overlapping generic bounds. 392 | 393 | ```rust 394 | /// If we also introduce a maybe-async 395 | /// version of the `From` trait… 396 | #[maybe(async)] 397 | pub trait From: Sized { 398 | #[maybe(async)] 399 | fn from(value: T) -> Self; 400 | } 401 | 402 | /// …we can implement the synchronous 403 | /// variant for any type `T, U: From`… 404 | impl Into for T 405 | where 406 | U: From {} 407 | 408 | /// …as well as the asynchronous variant for 409 | /// any type `T, U: async From`. 410 | impl async Into for T 411 | where 412 | U: async From {} 413 | ``` 414 | 415 | For the purpose of the trait resolver, `From` and `async From` should be 416 | considered non-overlapping bounds. This is a new capability which we'll need to 417 | introduce, and effectively comes down to treating `U: From` and `U: 418 | From` as non-overlapping bounds. Effect-generic trait bounds 419 | (conditional effects in bounds) are not introduced by this RFC, but may be introduced 420 | by a future extension. 421 | 422 | ## Super traits 423 | 424 | Super-trait hierarchies should be supported, as long as they are appropriately 425 | annotated. Say we wanted to define a maybe-async version of 426 | [`BufRead`](https://doc.rust-lang.org/std/io/trait.BufRead.html) which has 427 | `Read` as a supertrait. For that to work, the `Read` trait would also need to be 428 | marked maybe-async. That way if we implement the async version of `BufRead` we 429 | also require the async version of `Read` - idem for the non-async variants. 430 | 431 | ```rust 432 | #[maybe(async)] 433 | pub trait BufRead: #[maybe(async)] Read { 434 | #[maybe(async)] 435 | fn fill_buf(&mut self) -> Result<&[u8]>; 436 | #[maybe(async)] 437 | fn consume(&mut self, amt: usize); 438 | } 439 | ``` 440 | 441 | If a trait wants to have a non-async super-trait, it has to mark the super-trait 442 | as not being async. In the case that the supertrait eventually becomes generic 443 | over an effect, it's clear from the beginning which variant we 've chosen. 444 | 445 | ```rust 446 | #[maybe(async)] 447 | pub trait SuperTrait {} 448 | 449 | #[maybe(async)] 450 | pub trait SubTrait: #[not(async)] SuperTrait { } 451 | ``` 452 | 453 | Certain traits may want to guarantee ahead of time that they will never support 454 | a certain effect. For these traits it is possible to omit the effect marker, as 455 | the state of the effect is already unambiguous. It is expected most marker 456 | traits will want to be unambiguously never support for example the `async` 457 | effect. 458 | 459 | ```rust 460 | // The trait `Sized` guarantees it 461 | // will not ever be an `async trait`… 462 | #[not(async)] 463 | trait Sized {} 464 | 465 | // …which means it does not require annotations 466 | // when used as a supertrait. 467 | #[maybe(async)] 468 | trait Into: Sized { .. } 469 | ``` 470 | 471 | ## TODO: prerequisites 472 | 473 | - associated type defaults 474 | - complex where bounds on associated items removing the need for them to get implemented 475 | - a working demo of the constness effect 476 | - T-types buy-in (not before the old solver got removed) 477 | 478 | # Drawbacks 479 | [drawbacks]: #drawbacks 480 | 481 | ## Const effect states 482 | 483 | The `const` keyword in Rust has two meanings: 484 | 485 | - `const {}` blocks are always const-evaluated ("always" semantics) 486 | - `const fn` functions may be const evaluated ("maybe" semantics) 487 | 488 | Notably `const` does not provide a way to declare functions which must always 489 | const-evaluated. This RFC determines all traits and methods can be in one of 490 | four [states][#effect-states], including "always async" and "maybe async". As a 491 | result declaring a function which is "maybe-async" will syntactically appear 492 | different from a function which is "maybe-const". 493 | 494 | ## TODO: Additional syntax 495 | 496 | - we're adding some new syntax, that's going to be A Thing 497 | 498 | ## TODO: Direction 499 | 500 | - while not inherently closing any doors, we are kind of committing to the idea 501 | that we want to extend the stdlib to be effectful - that's the point 502 | - this has repercussions for how we structure our base traits and interfaces too 503 | 504 | # Prior art 505 | [prior-art]: #prior-art 506 | 507 | TODO: 508 | - swift: async polymorphism + rethrow 509 | - c++: noexcept + constexpr 510 | - koka: effect handlers (free monad) 511 | - rust: const fn 512 | - zig: maybe async functions 513 | 514 | # Unresolved questions 515 | [unresolved-questions]: #unresolved-questions 516 | 517 | * may want to use an associated const instead of a const generic 518 | 519 | ## TODO: Syntax 520 | 521 | - `#[maybe(async)]` is a placeholder 522 | - `maybe(async)` is clear but is verbose 523 | - `?async` is sigil-heavy, but has precedence in the trait system 524 | - `~async` is sigil-heavy, and also reserves a new sigil 525 | - `if/else` at the trait level does not create bidirectional relationships 526 | - `async` is less clear and verbose 527 | 528 | # Alternatives 529 | [alternatives]: #alternatives 530 | 531 | ## TODO: Do nothing (null hypothesis) 532 | 533 | - effect differences are inherent, which means they have to be solved somewhere 534 | - effect composition is where it gets bad; we have an async version of the stdlib, not an async + fallible version 535 | - things like linearity seem quite far out of reach right without this 536 | 537 | # Future possibilities 538 | [future-possibilities]: #future-possibilities 539 | 540 | ## TODO: Integration with other keywords 541 | 542 | - fallible functions 543 | - generator functions 544 | - linearity 545 | 546 | ## TODO: Effect-generic types and bodies 547 | 548 | - types 549 | - functions 550 | 551 | ```rust 552 | /// Before: the base implementation 553 | impl Into for Cat { 554 | fn into(self) -> Loaf { 555 | self.nap() 556 | } 557 | } 558 | 559 | /// Before: the async implementation 560 | impl async Into for AsyncCat { 561 | async fn into(self) -> AsyncLoaf { 562 | self.async_nap().await 563 | } 564 | } 565 | ``` 566 | 567 | ```rust 568 | // After: a single implementation 569 | ... 570 | ``` 571 | 572 | ## TODO: Effect sets 573 | 574 | - named effect sets 575 | - unify `core` and `std` via sets 576 | 577 | ## TODO: Normalize const 578 | 579 | - `const fn` is maybe-const 580 | - `const {}` is always const 581 | - this is super annoying lol, and that's why this system doesn't work for `const` right now 582 | --------------------------------------------------------------------------------