From 5ef6311c96b8502afd144957e98bbfa1bb117579 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 24 May 2023 19:35:18 -0400 Subject: [PATCH 01/20] RFC: Implementable trait aliases --- text/3437-implementable-trait-alias.md | 319 +++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 text/3437-implementable-trait-alias.md diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md new file mode 100644 index 00000000000..b0189224ae3 --- /dev/null +++ b/text/3437-implementable-trait-alias.md @@ -0,0 +1,319 @@ +- Feature Name: `trait_alias_impl` +- Start Date: 2023-05-24 +- RFC PR: [rust-lang/rfcs#3437](https://github.com/rust-lang/rfcs/pull/3437) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary + +Extend `#![feature(trait_alias)]` to permit `impl` blocks for trait aliases with a single primary trait. + +# Motivation + +Often, one desires to have a "weak" version of a trait, as well as a "strong" one providing additional guarantees. +Subtrait relationships are commonly used for this, but they sometimes fall short—expecially when the "strong" version is +expected to see more use, or was stabilized first. + +## Example: AFIT `Send` bound aliases + +### With subtraits + +Imagine a library, `frob-lib`, that provides a trait with an async method. (Think `tower::Service`.) + +```rust +//! crate frob-lib +pub trait Frobber { + async fn frob(&self); +} +``` + +Most of `frob-lib`'s users will need `Frobber::frob`'s return type to be `Send`, +so the library wants to make this common case as painless as possible. But non-`Send` +usage should be supported as well. + +`frob-lib`, following the recommended practice, decides to design its API in the following way: + +```rust +//! crate frob-lib + +pub trait LocalFrobber { + async fn frob(&self); +} + +// or whatever RTN syntax is decided on +pub trait Frobber: LocalFrobber + Send {} +impl Frobber for T where T: LocalFrobber + Send {} +``` + +These two traits are, in a sense, one trait with two forms: the "weak" `LocalFrobber`, and "strong" `Frobber` that offers an additional `Send` guarantee. + +Because `Frobber` (with `Send` bound) is the common case, `frob-lib`'s documentation and examples put it front and center. So naturally, Joe User tries to implement `Frobber` for his own type. + +```rust +//! crate joes-crate +use frob_lib::Frobber; + +struct MyType; + +impl Frobber for MyType { + async fn frob(&self) { + println!("Sloo is 120% klutzed. Initiating brop sequence...") + } +} +``` + +But one `cargo check` later, Joe is greeted with: + +``` +error[E0277]: the trait bound `MyType: LocalFrobber` is not satisfied + --> src/lib.rs:6:18 + | +6 | impl Frobber for MyType { + | ^^^^^^ the trait `LocalFrobber` is not implemented for `MyType` + +error[E0407]: method `frob` is not a member of trait `Frobber` + --> src/lib.rs:7:5 + | +7 | / async fn frob(&self) { +8 | | println!("Sloo is 120% klutzed. Initiating brop sequence...") +9 | | } + | |_____^ not a member of trait `Frobber` +``` + +Joe is confused. "What's a `LocalFrobber`? Isn't that only for non-`Send` use cases? Why do I need to care about all that?" But he eventually figures it out: + +```rust +//! crate joes-crate +use frob_lib::LocalFrobber; + +struct MyType; + +impl LocalFrobber for MyType { + #[refine] + async fn frob(&self) { + println!("Sloo is 120% klutzed. Initiating brop sequence...") + } +} +``` + +This is distinctly worse. Joe now has to reference both `Frobber` and `LocalFrobber` in his code, +and (assuming that the final AFIT feature ends up requiring it) also has to write `#[refine]`. + +### With today's `#![feature(trait_alias)]` + +What if `frob-lib` looked like this instead? + +```rust +//! crate frob-lib +#![feature(trait_alias)] + +pub trait LocalFrobber { + async fn frob(&self); +} + +pub trait Frobber = LocalFrobber + Send; +``` + +With today's `trait_alias`, it wouldn't make much difference for Joe. He would just get a slightly different +error message: + +``` +error[E0404]: expected trait, found trait alias `Frobber` + --> src/lib.rs:6:6 + | +6 | impl Frobber for MyType { + | ^^^^^^^ not a trait +``` + +## Speculative example: GATification of `Iterator` + +*This example relies on some language features that are currently pure speculation. +Implementable trait aliases are potentially necessary to support this use-case, but not sufficent.* + +Ever since the GAT MVP was stabilized, there has been discussion about how to add `LendingIterator` to the standard library, without breaking existing uses of `Iterator`. +The relationship between `LendingIterator` and `Iterator` is "weak"/"strong"—an `Iterator` is a `LendingIterator` with some extra guarantees about the `Item` associated type. + +Now, let's imagine that Rust had some form of "variance bounds", +that allowed restricting the way in which a type's GAT can depend on said GAT's generic parameters. +One could then define `Iterator` in terms of `LendingIterator`, like so: + +```rust +//! core::iter +pub trait LendingIterator { + type Item<'a> + where + Self: 'a; + + fn next(&'a mut self) -> Self::Item<'a>; +} + +pub trait Iterator = LendingIterator +where + // speculative syntax, just for the sake of this example + for<'a> Self::Item<'a>: bivariant_in<'a>; +``` + +But, as with the previous example, we are foiled by the fact that trait aliases aren't `impl`ementable, +so this change would break every `impl Iterator` block in existence. + +## Speculative example: `Async` trait + +There has been some discussion about a variant of the `Future` trait with an `unsafe` poll method, to support structured concurrency ([here](https://rust-lang.github.io/wg-async/vision/roadmap/scopes/capability/variant_async_trait.html) for example). *If* such a change ever happens, then the same "weak"/"strong" relationship will arise: the safe-to-poll `Future` trait would be a "strong" version of the unsafe-to-poll `Async`. As the linked design notes explain, there are major problems with expressing this relationship in today's Rust. + +# Guide-level explanation + +With `#![feature(trait_alias)]` (RFC #1733), one can define trait aliases, for use in bounds, trait objects, and `impl Trait`. This feature additionaly allows writing `impl` blocks for a subset of trait aliases. + +Let's rewrite our AFIT example from before, in terms of this feature. Here's what it looks like now: + +```rust +//! crate frob-lib +#![feature(trait_alias)] + +pub trait LocalFrobber { + async fn frob(&self); +} + +pub trait Frobber = LocalFrobber +where + // not `+ Send`! + Self: Send; +``` + +```rust +//! crate joes-crate +#![feature(trait_alias_impl)] + +use frob_lib::Frobber; + +struct MyType; + +impl Frobber for MyType { + async fn frob(&self) { + println!("Sloo is 120% klutzed. Initiating brop sequence...") + } +} +``` + +Joe's original code Just Works. + +The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, and paste it between the +`for` and `{` of a trait `impl` block, and the result is sytactically valid—then the trait alias is implementable. + +# Reference-level explanation + +A trait alias has the following syntax (using the Rust Reference's notation): + +> [Visibility](https://doc.rust-lang.org/stable/reference/visibility-and-privacy.html)? `trait` [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html) [GenericParams](https://doc.rust-lang.org/stable/reference/items/generics.html)? `=` [TypeParamBounds](https://doc.rust-lang.org/stable/reference/trait-bounds.html)? [WhereClause](https://doc.rust-lang.org/stable/reference/items/generics.html#where-clauses)? `;` + +For example, `trait Foo = PartialEq + Send where Self: Sync;` is a valid trait alias. + +Implementable trait aliases must follow a more restrictive form: + +> [Visibility](https://doc.rust-lang.org/stable/reference/visibility-and-privacy.html)? `trait` [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html) [GenericParams](https://doc.rust-lang.org/stable/reference/items/generics.html)? `=` [TypePath](https://doc.rust-lang.org/stable/reference/paths.html#paths-in-types) [WhereClause](https://doc.rust-lang.org/stable/reference/items/generics.html#where-clauses)? `;` + +For example, `trait Foo = PartialEq where Self: Sync;` is a valid implementable alias. The `=` must be followed by a single trait (or implementable trait alias), and then some number of where clauses. + +An impl block for a trait alias looks just like an impl block for the underlying trait. The alias's where clauses +are treated as obligations that the the `impl`ing type must meet. + +```rust +pub trait CopyIterator = Iterator where Self: Send; + +struct Foo; + +impl CopyIterator for Foo { + type Item = i32; // Would be an error if this was `String` + + fn next(&mut self) -> Self::Item { + 42 + } +} + +struct Bar; +impl !Send for Bar; + +// ERROR: `Bar` is not `Send` +// impl IntIterator for Bar { /* ... */ } +``` + +If the trait alias uniquely constrains a portion of the `impl` block, that part can be omitted. + +```rust +pub trait IntIterator = Iterator where Self: Send; + +struct Baz; + +impl IntIterator for Baz { + // The alias constrains `Self::Item` to `i32`, so we don't need to specify it + // (though we are allowed to do so if desired). + // type Item = i32; + + fn next(&mut self) -> i32 { + -27 + } +} +``` + +Trait aliases also allow omitting implied `#[refine]`s: + +```rust +//! crate frob-lib +#![feature(trait_alias)] + +pub trait LocalFrobber { + async fn frob(&self); +} + +// not `+ Send`! +pub trait Frobber = LocalFrobber where Self: Send; +``` + +```rust +//! crate joes-crate +#![feature(trait_alias_impl)] + +use frob_lib::Frobber; + +struct MyType; + +impl Frobber for MyType { + // The return future of this method is implicitly `Send`, as implied by the alias. + // No `#[refine]` is necessary. + async fn frob(&self) { + println!("Sloo is 120% klutzed. Initiating brop sequence...") + } +} +``` + +Trait aliases are `unsafe` to implement iff the underlying trait is marked `unsafe`. + +# Drawbacks + +- The sytactic distance between implementable and non-implementable aliases is short, which might confuse users. +In particular, the fact that `trait Foo = Bar + Send;` means something different than `trait Foo = Bar where Self: Send;` will likely be surprising to many. +- Adds complexity to the language. +- Many of the motivating use-cases involve language features that are not yet stable, or even merely speculative. +More experience with those features might unearth better alternatives. + +# Rationale and alternatives + +- Very lightweight, with no new syntax forms. Compare "trait transformers" proposals, for example—they are generally much heavier. +- Better ergonomics compared to purely proc-macro based solutions. +- One alternative is to allow marker traits or auto traits to appear in `+` bounds of implementable aliases. +(For example, `trait Foo = Bar + Send;` could be made implementable). However, I suspect that the complexity would not be worthwhile. +- Another possibility is to require an attribute on implmenentable aliase; e.g. `#[implementable] trait Foo = ...`. Again, I don't think that the complexity is warranted. + +# Prior art + +- [`trait_transformer` macro](https://github.com/google/impl_trait_utils) + +# Unresolved questions + +- How does `rustdoc` render these? Consider the `Frobber` example—ideally, `Frobber` should be emphasized +compared to `LocalFrobber`, but it's not clear how that would work. + +# Future possibilities + +- New kinds of bounds: anything that makes `where` clauses more powerful would make this feature more powerful as well. + - Variance bounds would allow this feature to support backward-compatible GATification. + - Method unsafety bounds would support the `Future` → `Async` use-case. From af7b7eaaba7664c0f555e3b628550da5078ee47b Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 24 May 2023 19:59:45 -0400 Subject: [PATCH 02/20] Fix wording --- text/3437-implementable-trait-alias.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index b0189224ae3..df84cc6c491 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -254,7 +254,7 @@ impl IntIterator for Baz { } ``` -Trait aliases also allow omitting implied `#[refine]`s: +Alias `impl`s also allow omitting implied `#[refine]`s: ```rust //! crate frob-lib From 6b36635d77f44cf877cd410e521c2fa49c7c63f5 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 15 Jun 2023 01:03:45 -0400 Subject: [PATCH 03/20] Discuss `#[implementable]` in more detail --- text/3437-implementable-trait-alias.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index df84cc6c491..dd4ccfac81e 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -291,6 +291,8 @@ Trait aliases are `unsafe` to implement iff the underlying trait is marked `unsa - The sytactic distance between implementable and non-implementable aliases is short, which might confuse users. In particular, the fact that `trait Foo = Bar + Send;` means something different than `trait Foo = Bar where Self: Send;` will likely be surprising to many. + - On the other hand, the rules mirror those of `impl` blocks, which Rust programmers already understand. + - Ideally, we would collect user feedback before stabilizing this feature. - Adds complexity to the language. - Many of the motivating use-cases involve language features that are not yet stable, or even merely speculative. More experience with those features might unearth better alternatives. @@ -300,8 +302,8 @@ More experience with those features might unearth better alternatives. - Very lightweight, with no new syntax forms. Compare "trait transformers" proposals, for example—they are generally much heavier. - Better ergonomics compared to purely proc-macro based solutions. - One alternative is to allow marker traits or auto traits to appear in `+` bounds of implementable aliases. -(For example, `trait Foo = Bar + Send;` could be made implementable). However, I suspect that the complexity would not be worthwhile. -- Another possibility is to require an attribute on implmenentable aliase; e.g. `#[implementable] trait Foo = ...`. Again, I don't think that the complexity is warranted. +(For example, `trait Foo = Bar + Send;` could be made implementable). However, this would arguably make implementability rules less intuitive, as the symmetry with `impl` blocks would be broken. +- Another possibility is to require an attribute on implmenentable aliase; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules explicit, but at the cost of cluttering the attribute namespace and adding more complexity to the language. # Prior art From 5aa38278ab4d65d60f91bf04ad6bcf979f2cab0e Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 15 Jun 2023 13:53:59 -0400 Subject: [PATCH 04/20] Add future possibility syntax --- text/3437-implementable-trait-alias.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index dd4ccfac81e..1b6aeec34e6 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -319,3 +319,4 @@ compared to `LocalFrobber`, but it's not clear how that would work. - New kinds of bounds: anything that makes `where` clauses more powerful would make this feature more powerful as well. - Variance bounds would allow this feature to support backward-compatible GATification. - Method unsafety bounds would support the `Future` → `Async` use-case. +- Allow `trait Foo: Copy = Iterator;` as alternative to `trait Foo = Iterator where Self: Copy;`. From cd871e0ceb055938a7af8b0762a0b066871161b6 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 13 Sep 2023 12:34:56 -0400 Subject: [PATCH 05/20] Support fully-qualified method call syntax --- text/3437-implementable-trait-alias.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 1b6aeec34e6..8de730ea216 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -5,7 +5,7 @@ # Summary -Extend `#![feature(trait_alias)]` to permit `impl` blocks for trait aliases with a single primary trait. +Extend `#![feature(trait_alias)]` to permit `impl` blocks for trait aliases with a single primary trait. Also support fully-qualified method call syntax with such aliases. # Motivation @@ -196,7 +196,7 @@ impl Frobber for MyType { Joe's original code Just Works. -The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, and paste it between the +The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, paste it between the `for` and `{` of a trait `impl` block, and the result is sytactically valid—then the trait alias is implementable. # Reference-level explanation @@ -287,6 +287,25 @@ impl Frobber for MyType { Trait aliases are `unsafe` to implement iff the underlying trait is marked `unsafe`. +Implementable trait aliases can also be used with trait-qualified and fully-qualified method call syntax. When used this way, +they are treated equivalently to the underlying primary trait, with the additional restriction that all `where` clauses and associated type bounds +must be satisfied. + +```rust +trait IntIter = Iterator where Self: Clone; + +fn foo() { + let iter = [1_u32].into_iter(); + IntIter::next(&mut iter); // works + ::next(); // works + //IntIter::clone(&iter); // ERROR: trait `Iterator` has no method named `clone()` + let dyn_iter: &mut dyn Iterator = &mut iter; + //IntIter::next(dyn_iter); // ERROR: `dyn Iterator` does not implement `Clone` + let signed_iter = [1_i32].into_iter(); + //IntIter::next(&mut signed_iter); // ERROR: Expected `::Item` to be `u32`, it is `i32` +} +``` + # Drawbacks - The sytactic distance between implementable and non-implementable aliases is short, which might confuse users. From 5d1080abcf68e53e181f4e83804352f815d37ab4 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 13 Sep 2023 12:57:18 -0400 Subject: [PATCH 06/20] Allow implementable aliases in paths more generally --- text/3437-implementable-trait-alias.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 8de730ea216..145e1121259 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -287,8 +287,7 @@ impl Frobber for MyType { Trait aliases are `unsafe` to implement iff the underlying trait is marked `unsafe`. -Implementable trait aliases can also be used with trait-qualified and fully-qualified method call syntax. When used this way, -they are treated equivalently to the underlying primary trait, with the additional restriction that all `where` clauses and associated type bounds +Implementable trait aliases can also be used with trait-qualified and fully-qualified method call syntax, as well as in paths more generally. When used this way, they are treated equivalently to the underlying primary trait, with the additional restriction that all `where` clauses and associated type bounds must be satisfied. ```rust @@ -296,8 +295,8 @@ trait IntIter = Iterator where Self: Clone; fn foo() { let iter = [1_u32].into_iter(); - IntIter::next(&mut iter); // works - ::next(); // works + let _: IntIter::Item = IntIter::next(&mut iter); // works + let _: ::Item = ::next(); // works //IntIter::clone(&iter); // ERROR: trait `Iterator` has no method named `clone()` let dyn_iter: &mut dyn Iterator = &mut iter; //IntIter::next(dyn_iter); // ERROR: `dyn Iterator` does not implement `Clone` From 13a9c18a013e485f17b7602b8007b422adc30254 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sun, 17 Sep 2023 20:58:11 -0400 Subject: [PATCH 07/20] Add discussion of "unioning" traits --- text/3437-implementable-trait-alias.md | 43 +++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 145e1121259..046bcf4ba92 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -157,7 +157,7 @@ so this change would break every `impl Iterator` block in existence. ## Speculative example: `Async` trait -There has been some discussion about a variant of the `Future` trait with an `unsafe` poll method, to support structured concurrency ([here](https://rust-lang.github.io/wg-async/vision/roadmap/scopes/capability/variant_async_trait.html) for example). *If* such a change ever happens, then the same "weak"/"strong" relationship will arise: the safe-to-poll `Future` trait would be a "strong" version of the unsafe-to-poll `Async`. As the linked design notes explain, there are major problems with expressing this relationship in today's Rust. +There has been some discussion about a variant of the `Future` trait with an `unsafe` poll method, to support structured concurrency ([here](https://rust-lang.github.io/wg-async/vision/roadmap/scopes/capability/variant_async_trait.html) for example). *If* such a change ever happens, then the same "weak"/"strong" relationship will arise: the safe-to-poll `Future` trait would be a "strong" version of the unsafe-to-poll `Async`. As the linked design notes explain, there are major problems with expressing that relationship in today's Rust. # Guide-level explanation @@ -323,6 +323,47 @@ More experience with those features might unearth better alternatives. (For example, `trait Foo = Bar + Send;` could be made implementable). However, this would arguably make implementability rules less intuitive, as the symmetry with `impl` blocks would be broken. - Another possibility is to require an attribute on implmenentable aliase; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules explicit, but at the cost of cluttering the attribute namespace and adding more complexity to the language. +## What about combining multiple prtimary traits, and their items, into one impl block? + +It's possible to imagine an extension of this proposal, that allows trait aliases to be implementable even if they have multiple primary traits. For example: + +```rust +trait Foo = Clone + PartialEq; + +struct Stu; + +impl Foo for Stu { + fn clone(&self) -> Self { + Stu + } + + fn eq(&self, other: &Self) -> bool { + true + } +} +``` + +Such a feature could be useful when a trait has multiple items and you want to split it in two. + +However, there are some issues. Most glaring is the risk of name collisions: + +```rust +trait A { + fn foo(); +} + +trait B { + fn foo(); +} + +// How would you write an `impl` block for this? +trait C = A + B; +``` + +Such a feature could also make it harder to find the declaration of a trait item from its implementation, especially if IDE "go to definition" is not available. One would need to first find the trait alias definition, and then look through every primary trait to find the item. (However, given the current situation with postfix method call syntax, maybe this is an acceptable tradeoff.) + +Perhaps a more narrowly tailored version of this extension, in which both subtrait and supertrait explicitly opt-in to support sharing an `impl` block with one another, would satisfy the backward-compatibility use-case while avoiding the above issues. I think exploring that is best left to a future RFC. + # Prior art - [`trait_transformer` macro](https://github.com/google/impl_trait_utils) From 1b32952631c6505fe5b18204f624cd6cd2545bfa Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sun, 17 Sep 2023 21:00:24 -0400 Subject: [PATCH 08/20] Fix typos --- text/3437-implementable-trait-alias.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 046bcf4ba92..54a91ad2d3b 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -321,7 +321,7 @@ More experience with those features might unearth better alternatives. - Better ergonomics compared to purely proc-macro based solutions. - One alternative is to allow marker traits or auto traits to appear in `+` bounds of implementable aliases. (For example, `trait Foo = Bar + Send;` could be made implementable). However, this would arguably make implementability rules less intuitive, as the symmetry with `impl` blocks would be broken. -- Another possibility is to require an attribute on implmenentable aliase; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules explicit, but at the cost of cluttering the attribute namespace and adding more complexity to the language. +- Another possibility is to require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules explicit, at the cost of cluttering the attribute namespace. ## What about combining multiple prtimary traits, and their items, into one impl block? From f03d3b95205e19b22e62076286373716867580f9 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 27 Sep 2023 15:17:54 -0400 Subject: [PATCH 09/20] Expand alternatives, fix implementability rules hole --- text/3437-implementable-trait-alias.md | 82 +++++++++++++++----------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 54a91ad2d3b..8302fa9e4e8 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -9,9 +9,7 @@ Extend `#![feature(trait_alias)]` to permit `impl` blocks for trait aliases with # Motivation -Often, one desires to have a "weak" version of a trait, as well as a "strong" one providing additional guarantees. -Subtrait relationships are commonly used for this, but they sometimes fall short—expecially when the "strong" version is -expected to see more use, or was stabilized first. +Often, one desires to have a "weak" version of a trait, as well as a "strong" one providing additional guarantees. Subtrait relationships are commonly used for this, but they sometimes fall short—expecially when the "strong" version is expected to see more use, or was stabilized first. ## Example: AFIT `Send` bound aliases @@ -26,9 +24,7 @@ pub trait Frobber { } ``` -Most of `frob-lib`'s users will need `Frobber::frob`'s return type to be `Send`, -so the library wants to make this common case as painless as possible. But non-`Send` -usage should be supported as well. +Most of `frob-lib`'s users will need `Frobber::frob`'s return type to be `Send`, so the library wants to make this common case as painless as possible. But non-`Send` usage should be supported as well. `frob-lib`, following the recommended practice, decides to design its API in the following way: @@ -95,8 +91,7 @@ impl LocalFrobber for MyType { } ``` -This is distinctly worse. Joe now has to reference both `Frobber` and `LocalFrobber` in his code, -and (assuming that the final AFIT feature ends up requiring it) also has to write `#[refine]`. +This is distinctly worse. Joe now has to reference both `Frobber` and `LocalFrobber` in his code, and (assuming that the final AFIT feature ends up requiring it) also has to write `#[refine]`. ### With today's `#![feature(trait_alias)]` @@ -113,8 +108,7 @@ pub trait LocalFrobber { pub trait Frobber = LocalFrobber + Send; ``` -With today's `trait_alias`, it wouldn't make much difference for Joe. He would just get a slightly different -error message: +With today's `trait_alias`, it wouldn't make much difference for Joe. He would just get a slightly different error message: ``` error[E0404]: expected trait, found trait alias `Frobber` @@ -126,15 +120,11 @@ error[E0404]: expected trait, found trait alias `Frobber` ## Speculative example: GATification of `Iterator` -*This example relies on some language features that are currently pure speculation. -Implementable trait aliases are potentially necessary to support this use-case, but not sufficent.* +*This example relies on some language features that are currently pure speculation.Implementable trait aliases are potentially necessary to support this use-case, but not sufficent.* -Ever since the GAT MVP was stabilized, there has been discussion about how to add `LendingIterator` to the standard library, without breaking existing uses of `Iterator`. -The relationship between `LendingIterator` and `Iterator` is "weak"/"strong"—an `Iterator` is a `LendingIterator` with some extra guarantees about the `Item` associated type. +Ever since the GAT MVP was stabilized, there has been discussion about how to add `LendingIterator` to the standard library, without breaking existing uses of `Iterator`. The relationship between `LendingIterator` and `Iterator` is "weak"/"strong"—an `Iterator` is a `LendingIterator` with some extra guarantees about the `Item` associated type. -Now, let's imagine that Rust had some form of "variance bounds", -that allowed restricting the way in which a type's GAT can depend on said GAT's generic parameters. -One could then define `Iterator` in terms of `LendingIterator`, like so: +Now, let's imagine that Rust had some form of "variance bounds", that allowed restricting the way in which a type's GAT can depend on said GAT's generic parameters. One could then define `Iterator` in terms of `LendingIterator`, like so: ```rust //! core::iter @@ -152,8 +142,7 @@ where for<'a> Self::Item<'a>: bivariant_in<'a>; ``` -But, as with the previous example, we are foiled by the fact that trait aliases aren't `impl`ementable, -so this change would break every `impl Iterator` block in existence. +But, as with the previous example, we are foiled by the fact that trait aliases aren't `impl`ementable, so this change would break every `impl Iterator` block in existence. ## Speculative example: `Async` trait @@ -196,8 +185,7 @@ impl Frobber for MyType { Joe's original code Just Works. -The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, paste it between the -`for` and `{` of a trait `impl` block, and the result is sytactically valid—then the trait alias is implementable. +The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, paste it between the `for` and `{` of a trait `impl` block, and the result is sytactically valid—then the trait alias is most likely implementable. # Reference-level explanation @@ -213,8 +201,7 @@ Implementable trait aliases must follow a more restrictive form: For example, `trait Foo = PartialEq where Self: Sync;` is a valid implementable alias. The `=` must be followed by a single trait (or implementable trait alias), and then some number of where clauses. -An impl block for a trait alias looks just like an impl block for the underlying trait. The alias's where clauses -are treated as obligations that the the `impl`ing type must meet. +An impl block for a trait alias looks just like an impl block for the underlying trait. The alias's where clauses are treated as if they had been written out in the `impl` header. ```rust pub trait CopyIterator = Iterator where Self: Send; @@ -236,6 +223,29 @@ impl !Send for Bar; // impl IntIterator for Bar { /* ... */ } ``` +There is another restriction that trait aliases must adhere to in order to be implementable: all generic parameters of the alias itself must be used as generic parameters of the alias's primary trait. + +```rust +// Implementable +trait Foo = PartialEq; + +// Not implementable +trait Foo = Copy; +trait Foo = Copy where T: Send; +trait Foo = Iterator; +trait Foo = Copy where Self: PartialEq; +``` + +Bounds on such generic parameters are enforced at the `impl` site. + +```rust +trait Underlying {} + +trait Alias = Underlying; + +impl Alias for i32 {} // Error: missing `T: Send` bound +``` + If the trait alias uniquely constrains a portion of the `impl` block, that part can be omitted. ```rust @@ -287,8 +297,7 @@ impl Frobber for MyType { Trait aliases are `unsafe` to implement iff the underlying trait is marked `unsafe`. -Implementable trait aliases can also be used with trait-qualified and fully-qualified method call syntax, as well as in paths more generally. When used this way, they are treated equivalently to the underlying primary trait, with the additional restriction that all `where` clauses and associated type bounds -must be satisfied. +Implementable trait aliases can also be used with trait-qualified and fully-qualified method call syntax, as well as in paths more generally. When used this way, they are treated equivalently to the underlying primary trait, with the additional restriction that all `where` clauses and type parameter/associated type bounds must be satisfied. ```rust trait IntIter = Iterator where Self: Clone; @@ -307,23 +316,24 @@ fn foo() { # Drawbacks -- The sytactic distance between implementable and non-implementable aliases is short, which might confuse users. -In particular, the fact that `trait Foo = Bar + Send;` means something different than `trait Foo = Bar where Self: Send;` will likely be surprising to many. +- The sytactic distance between implementable and non-implementable aliases is short, which might confuse users. In particular, the fact that `trait Foo = Bar + Send;` means something different than `trait Foo = Bar where Self: Send;` will likely be surprising to many. - On the other hand, the rules mirror those of `impl` blocks, which Rust programmers already understand. - Ideally, we would collect user feedback before stabilizing this feature. -- Adds complexity to the language. -- Many of the motivating use-cases involve language features that are not yet stable, or even merely speculative. -More experience with those features might unearth better alternatives. +- Adds complexity to the language, which might surprise or confuse users. +- Many of the motivating use-cases involve language features that are not yet stable, or even merely speculative. More experience with those features might unearth better alternatives. # Rationale and alternatives - Very lightweight, with no new syntax forms. Compare "trait transformers" proposals, for example—they are generally much heavier. - Better ergonomics compared to purely proc-macro based solutions. - One alternative is to allow marker traits or auto traits to appear in `+` bounds of implementable aliases. -(For example, `trait Foo = Bar + Send;` could be made implementable). However, this would arguably make implementability rules less intuitive, as the symmetry with `impl` blocks would be broken. -- Another possibility is to require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules explicit, at the cost of cluttering the attribute namespace. +(For example, `trait Foo = Bar + Send;` could be made implementable). + - This may make the implementablility rules more intutive to some, as the distinction between `+ Send` and `where Self: Send` would no longer be present. + - However, it also might make the rules less intuitive, as the symmetry with `impl` blocks would be broken. + - Again, user feedback could help make this decision. +- Another option is to require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules more explicit, at the cost of cluttering user code and the attribute namespace. -## What about combining multiple prtimary traits, and their items, into one impl block? +## What about combining multiple primary traits, and their items, into one impl block? It's possible to imagine an extension of this proposal, that allows trait aliases to be implementable even if they have multiple primary traits. For example: @@ -370,12 +380,12 @@ Perhaps a more narrowly tailored version of this extension, in which both subtra # Unresolved questions -- How does `rustdoc` render these? Consider the `Frobber` example—ideally, `Frobber` should be emphasized -compared to `LocalFrobber`, but it's not clear how that would work. +- How does `rustdoc` render these? Consider the `Frobber` example—ideally, `Frobber` should be emphasized compared to `LocalFrobber`, but it's not clear how that would work. # Future possibilities - New kinds of bounds: anything that makes `where` clauses more powerful would make this feature more powerful as well. - Variance bounds would allow this feature to support backward-compatible GATification. - Method unsafety bounds would support the `Future` → `Async` use-case. -- Allow `trait Foo: Copy = Iterator;` as alternative to `trait Foo = Iterator where Self: Copy;`. +- `trait Foo: Copy = Iterator;` could be allowed as an alternative to `trait Foo = Iterator where Self: Copy;`. +- The possible contents of `impl` bodies could be expanded, for example to support combining supertrait and subtrait implementations. From d2603ab286fdae801062570d1102a0833b0b070e Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Fri, 6 Oct 2023 14:05:18 -0400 Subject: [PATCH 10/20] Add words about associated type bounds --- text/3437-implementable-trait-alias.md | 67 ++++++++++++++++---------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 8302fa9e4e8..b3177879ccd 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -189,6 +189,8 @@ The rule of thumb is: if you can copy everything between the `=` and `;` of a tr # Reference-level explanation +## Implementability rules + A trait alias has the following syntax (using the Rust Reference's notation): > [Visibility](https://doc.rust-lang.org/stable/reference/visibility-and-privacy.html)? `trait` [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html) [GenericParams](https://doc.rust-lang.org/stable/reference/items/generics.html)? `=` [TypeParamBounds](https://doc.rust-lang.org/stable/reference/trait-bounds.html)? [WhereClause](https://doc.rust-lang.org/stable/reference/items/generics.html#where-clauses)? `;` @@ -199,7 +201,22 @@ Implementable trait aliases must follow a more restrictive form: > [Visibility](https://doc.rust-lang.org/stable/reference/visibility-and-privacy.html)? `trait` [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html) [GenericParams](https://doc.rust-lang.org/stable/reference/items/generics.html)? `=` [TypePath](https://doc.rust-lang.org/stable/reference/paths.html#paths-in-types) [WhereClause](https://doc.rust-lang.org/stable/reference/items/generics.html#where-clauses)? `;` -For example, `trait Foo = PartialEq where Self: Sync;` is a valid implementable alias. The `=` must be followed by a single trait (or implementable trait alias), and then some number of where clauses. +For example, `trait Foo = PartialEq where Self: Sync;` is a valid implementable alias. The `=` must be followed by a single trait (or implementable trait alias), and then some number of where clauses. The trait's generic parameter list may contain associated type constraints (for example `trait IntIterator = Iterator`). + +There is another restriction that trait aliases must adhere to in order to be implementable: all generic parameters of the alias itself must be used as generic parameters of the alias's primary trait. + +```rust +// Implementable +trait Foo = PartialEq; + +// Not implementable +trait Foo = Copy; +trait Foo = Copy where T: Send; +trait Foo = Iterator; +trait Foo = Copy where Self: PartialEq; +``` + +## Usage in `impl` blocks An impl block for a trait alias looks just like an impl block for the underlying trait. The alias's where clauses are treated as if they had been written out in the `impl` header. @@ -223,27 +240,14 @@ impl !Send for Bar; // impl IntIterator for Bar { /* ... */ } ``` -There is another restriction that trait aliases must adhere to in order to be implementable: all generic parameters of the alias itself must be used as generic parameters of the alias's primary trait. - -```rust -// Implementable -trait Foo = PartialEq; - -// Not implementable -trait Foo = Copy; -trait Foo = Copy where T: Send; -trait Foo = Iterator; -trait Foo = Copy where Self: PartialEq; -``` - -Bounds on such generic parameters are enforced at the `impl` site. +Bounds on generic parameters are also enforced at the `impl` site. ```rust trait Underlying {} trait Alias = Underlying; -impl Alias for i32 {} // Error: missing `T: Send` bound +impl Alias<*const i32> for i32 {} // Error: `*const i32` is not `Send` ``` If the trait alias uniquely constrains a portion of the `impl` block, that part can be omitted. @@ -297,21 +301,32 @@ impl Frobber for MyType { Trait aliases are `unsafe` to implement iff the underlying trait is marked `unsafe`. +## Usage in paths + Implementable trait aliases can also be used with trait-qualified and fully-qualified method call syntax, as well as in paths more generally. When used this way, they are treated equivalently to the underlying primary trait, with the additional restriction that all `where` clauses and type parameter/associated type bounds must be satisfied. ```rust trait IntIter = Iterator where Self: Clone; -fn foo() { - let iter = [1_u32].into_iter(); - let _: IntIter::Item = IntIter::next(&mut iter); // works - let _: ::Item = ::next(); // works - //IntIter::clone(&iter); // ERROR: trait `Iterator` has no method named `clone()` - let dyn_iter: &mut dyn Iterator = &mut iter; - //IntIter::next(dyn_iter); // ERROR: `dyn Iterator` does not implement `Clone` - let signed_iter = [1_i32].into_iter(); - //IntIter::next(&mut signed_iter); // ERROR: Expected `::Item` to be `u32`, it is `i32` -} +let iter = [1_u32].into_iter(); +let _: IntIter::Item = IntIter::next(&mut iter); // works +let _: ::Item = ::next(); // works +//IntIter::clone(&iter); // ERROR: trait `Iterator` has no method named `clone()` +let dyn_iter: &mut dyn Iterator = &mut iter; +//IntIter::next(dyn_iter); // ERROR: `dyn Iterator` does not implement `Clone` +let signed_iter = [1_i32].into_iter(); +//IntIter::next(&mut signed_iter); // ERROR: Expected `::Item` to be `u32`, it is `i32` +``` + +Implementable trait aliases can also be used with associated type bounds; the associated type must belong to the alias's primary trait. + +```rust +trait IteratorAlias = Iterator; +let _: IteratorAlias = [1_u32].into_iter(); + +trait IntIter = Iterator where Self: Clone; +let _: IntIter = [1_u32].into_iter(); // `Item = u32` is redundant, but allowed +//let _: IntIter = [1.0_f64].into_iter(); // ERROR: `Item = f64` conflicts with `Item = u32` ``` # Drawbacks From 1e235ffa883663708a38dc7c8c2c77928b9c6af7 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 25 Oct 2023 13:04:15 -0400 Subject: [PATCH 11/20] Fix typo --- text/3437-implementable-trait-alias.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index b3177879ccd..12d39bc1e2c 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -120,7 +120,7 @@ error[E0404]: expected trait, found trait alias `Frobber` ## Speculative example: GATification of `Iterator` -*This example relies on some language features that are currently pure speculation.Implementable trait aliases are potentially necessary to support this use-case, but not sufficent.* +*This example relies on some language features that are currently pure speculation. Implementable trait aliases are potentially necessary to support this use-case, but not sufficent.* Ever since the GAT MVP was stabilized, there has been discussion about how to add `LendingIterator` to the standard library, without breaking existing uses of `Iterator`. The relationship between `LendingIterator` and `Iterator` is "weak"/"strong"—an `Iterator` is a `LendingIterator` with some extra guarantees about the `Item` associated type. From 6c22c39009f94b3255032cae700ec153fdfd1833 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 25 Oct 2023 17:16:12 -0400 Subject: [PATCH 12/20] Address biggest comments from design meeting --- text/3437-implementable-trait-alias.md | 106 ++++++++++++++++++------- 1 file changed, 79 insertions(+), 27 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 12d39bc1e2c..0bac427c173 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -9,7 +9,7 @@ Extend `#![feature(trait_alias)]` to permit `impl` blocks for trait aliases with # Motivation -Often, one desires to have a "weak" version of a trait, as well as a "strong" one providing additional guarantees. Subtrait relationships are commonly used for this, but they sometimes fall short—expecially when the "strong" version is expected to see more use, or was stabilized first. +Often, one desires to have a "weak" version of a trait, as well as a "strong" one providing additional guarantees. Subtrait relationships are commonly used for this, but they sometimes fall short—especially when the "strong" version is expected to see more use, or was stabilized first. ## Example: AFIT `Send` bound aliases @@ -120,7 +120,7 @@ error[E0404]: expected trait, found trait alias `Frobber` ## Speculative example: GATification of `Iterator` -*This example relies on some language features that are currently pure speculation. Implementable trait aliases are potentially necessary to support this use-case, but not sufficent.* +*This example relies on some language features that are currently pure speculation. Implementable trait aliases are potentially necessary to support this use-case, but not sufficient.* Ever since the GAT MVP was stabilized, there has been discussion about how to add `LendingIterator` to the standard library, without breaking existing uses of `Iterator`. The relationship between `LendingIterator` and `Iterator` is "weak"/"strong"—an `Iterator` is a `LendingIterator` with some extra guarantees about the `Item` associated type. @@ -150,7 +150,7 @@ There has been some discussion about a variant of the `Future` trait with an `un # Guide-level explanation -With `#![feature(trait_alias)]` (RFC #1733), one can define trait aliases, for use in bounds, trait objects, and `impl Trait`. This feature additionaly allows writing `impl` blocks for a subset of trait aliases. +With `#![feature(trait_alias)]` (RFC #1733), one can define trait aliases, for use in bounds, trait objects, and `impl Trait`. This feature additionally allows writing `impl` blocks for a subset of trait aliases. Let's rewrite our AFIT example from before, in terms of this feature. Here's what it looks like now: @@ -185,7 +185,7 @@ impl Frobber for MyType { Joe's original code Just Works. -The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, paste it between the `for` and `{` of a trait `impl` block, and the result is sytactically valid—then the trait alias is most likely implementable. +The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, paste it between the `for` and `{` of a trait `impl` block, and the result is syntactically valid—then the trait alias is most likely implementable. # Reference-level explanation @@ -203,22 +203,9 @@ Implementable trait aliases must follow a more restrictive form: For example, `trait Foo = PartialEq where Self: Sync;` is a valid implementable alias. The `=` must be followed by a single trait (or implementable trait alias), and then some number of where clauses. The trait's generic parameter list may contain associated type constraints (for example `trait IntIterator = Iterator`). -There is another restriction that trait aliases must adhere to in order to be implementable: all generic parameters of the alias itself must be used as generic parameters of the alias's primary trait. - -```rust -// Implementable -trait Foo = PartialEq; - -// Not implementable -trait Foo = Copy; -trait Foo = Copy where T: Send; -trait Foo = Iterator; -trait Foo = Copy where Self: PartialEq; -``` - ## Usage in `impl` blocks -An impl block for a trait alias looks just like an impl block for the underlying trait. The alias's where clauses are treated as if they had been written out in the `impl` header. +An `impl` block for a trait alias looks just like an `impl` block for the underlying trait. The alias's where clauses are enforced as requirements that the `impl`ing type must meet—just like `where` clauses in trait declarations are treated. ```rust pub trait CopyIterator = Iterator where Self: Send; @@ -236,8 +223,19 @@ impl CopyIterator for Foo { struct Bar; impl !Send for Bar; -// ERROR: `Bar` is not `Send` -// impl IntIterator for Bar { /* ... */ } +//impl CopyIterator for Bar { /* ... */ } // ERROR: `Bar` is not `Send` +``` + +```rust +trait Foo {} +trait Bar = Foo where Self: Send; +//impl Bar for T {} // ERROR: Need to add `T: Send` bound +``` +```rust +#![feature(trivial_bounds)] +trait Foo {} +trait Bar = Foo where String: Copy; +//impl Bar for () {} // ERROR: `String: Copy` not satisfied ``` Bounds on generic parameters are also enforced at the `impl` site. @@ -268,6 +266,28 @@ impl IntIterator for Baz { } ``` +Such constraints can be inferred indirectly: + +```rust +trait Bar: Iterator {} +pub trait IntIterator = Iterator where Self: Bar; + +struct Baz; + +impl Bar for Baz {} + +impl IntIterator for Baz { + // `IntIterator` requires `Bar`, + // which requires `Iterator`, + // so `Item` must be `i32` + // and we don't need to specify it. + + fn next(&mut self) -> i32 { + -27 + } +} +``` + Alias `impl`s also allow omitting implied `#[refine]`s: ```rust @@ -306,11 +326,13 @@ Trait aliases are `unsafe` to implement iff the underlying trait is marked `unsa Implementable trait aliases can also be used with trait-qualified and fully-qualified method call syntax, as well as in paths more generally. When used this way, they are treated equivalently to the underlying primary trait, with the additional restriction that all `where` clauses and type parameter/associated type bounds must be satisfied. ```rust +use std::array; + trait IntIter = Iterator where Self: Clone; let iter = [1_u32].into_iter(); let _: IntIter::Item = IntIter::next(&mut iter); // works -let _: ::Item = ::next(); // works +let _: ::Item = ::next(); // works //IntIter::clone(&iter); // ERROR: trait `Iterator` has no method named `clone()` let dyn_iter: &mut dyn Iterator = &mut iter; //IntIter::next(dyn_iter); // ERROR: `dyn Iterator` does not implement `Clone` @@ -331,9 +353,7 @@ let _: IntIter = [1_u32].into_iter(); // `Item = u32` is redundant, # Drawbacks -- The sytactic distance between implementable and non-implementable aliases is short, which might confuse users. In particular, the fact that `trait Foo = Bar + Send;` means something different than `trait Foo = Bar where Self: Send;` will likely be surprising to many. - - On the other hand, the rules mirror those of `impl` blocks, which Rust programmers already understand. - - Ideally, we would collect user feedback before stabilizing this feature. +- The syntactic distance between implementable and non-implementable aliases is short, which might confuse users. In particular, the fact that `trait Foo = Bar + Send;` means something different than `trait Foo = Bar where Self: Send;` will likely be surprising to many. - Adds complexity to the language, which might surprise or confuse users. - Many of the motivating use-cases involve language features that are not yet stable, or even merely speculative. More experience with those features might unearth better alternatives. @@ -343,12 +363,43 @@ let _: IntIter = [1_u32].into_iter(); // `Item = u32` is redundant, - Better ergonomics compared to purely proc-macro based solutions. - One alternative is to allow marker traits or auto traits to appear in `+` bounds of implementable aliases. (For example, `trait Foo = Bar + Send;` could be made implementable). - - This may make the implementablility rules more intutive to some, as the distinction between `+ Send` and `where Self: Send` would no longer be present. + - This may make the implementablility rules more intuitive to some, as the distinction between `+ Send` and `where Self: Send` would no longer be present. - However, it also might make the rules less intuitive, as the symmetry with `impl` blocks would be broken. + - Also, such a change might break the commutativity of `+`, or make it less obvious which trait is being implemented. - Again, user feedback could help make this decision. - Another option is to require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules more explicit, at the cost of cluttering user code and the attribute namespace. +- A previous version of this RFC required type parameters of implementable trait aliases to be used as type parameters of the alias's primary trait. This restriction was meant to avoid surprising errors: + +```rust +trait Foo = Copy; + +#[derive(Clone)] +struct MyType; + +impl Foo for MyType {} // ERROR: `T`` is unconstrained +``` + +```rust +trait Foo = Iterator; + +struct MyType; + +impl Foo for MyType { + fn next(&mut Self) -> Option { + todo!() + } +} + +impl Foo for MyType { // ERROR: overlapping impls + fn next(&mut Self) -> Option { + todo!() + } +} +``` + +However, upon further discussion, I now lean toward allowing more flexibility, even at the risk of potential confusion. -## What about combining multiple primary traits, and their items, into one impl block? +## What about combining multiple primary traits, and their items, into one `impl` block? It's possible to imagine an extension of this proposal, that allows trait aliases to be implementable even if they have multiple primary traits. For example: @@ -385,7 +436,7 @@ trait B { trait C = A + B; ``` -Such a feature could also make it harder to find the declaration of a trait item from its implementation, especially if IDE "go to definition" is not available. One would need to first find the trait alias definition, and then look through every primary trait to find the item. (However, given the current situation with postfix method call syntax, maybe this is an acceptable tradeoff.) +Such a feature could also make it harder to find the declaration of a trait item from its implementation, especially if IDE "go to definition" is not available. One would need to first find the trait alias definition, and then look through every primary trait to find the item. (However, given the current situation with postfix method call syntax, maybe this is an acceptable trade-off.) Perhaps a more narrowly tailored version of this extension, in which both subtrait and supertrait explicitly opt-in to support sharing an `impl` block with one another, would satisfy the backward-compatibility use-case while avoiding the above issues. I think exploring that is best left to a future RFC. @@ -403,4 +454,5 @@ Perhaps a more narrowly tailored version of this extension, in which both subtra - Variance bounds would allow this feature to support backward-compatible GATification. - Method unsafety bounds would support the `Future` → `Async` use-case. - `trait Foo: Copy = Iterator;` could be allowed as an alternative to `trait Foo = Iterator where Self: Copy;`. +- `impl Trait for Type { /* ... */ }` could be permitted in the future, to make the "copy-paste" rule of thumb work better. - The possible contents of `impl` bodies could be expanded, for example to support combining supertrait and subtrait implementations. From 149b016b1eef3afda3d1fa0a2125f4e86974ffef Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 25 Oct 2023 17:24:07 -0400 Subject: [PATCH 13/20] Update motivation to reflect current state of AFIT --- text/3437-implementable-trait-alias.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 0bac427c173..9d1c028f417 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -84,14 +84,13 @@ use frob_lib::LocalFrobber; struct MyType; impl LocalFrobber for MyType { - #[refine] async fn frob(&self) { println!("Sloo is 120% klutzed. Initiating brop sequence...") } } ``` -This is distinctly worse. Joe now has to reference both `Frobber` and `LocalFrobber` in his code, and (assuming that the final AFIT feature ends up requiring it) also has to write `#[refine]`. +This is distinctly worse; Joe now has to reference both `Frobber` and `LocalFrobber` in his code. ### With today's `#![feature(trait_alias)]` From 7089998d027c73e117a62602b2cc6ea616ebc920 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 25 Oct 2023 17:27:52 -0400 Subject: [PATCH 14/20] Correct minor error --- text/3437-implementable-trait-alias.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 9d1c028f417..682c6cd7663 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -367,7 +367,7 @@ let _: IntIter = [1_u32].into_iter(); // `Item = u32` is redundant, - Also, such a change might break the commutativity of `+`, or make it less obvious which trait is being implemented. - Again, user feedback could help make this decision. - Another option is to require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules more explicit, at the cost of cluttering user code and the attribute namespace. -- A previous version of this RFC required type parameters of implementable trait aliases to be used as type parameters of the alias's primary trait. This restriction was meant to avoid surprising errors: +- A previous version of this RFC required generic parameters of implementable trait aliases to be used as generic parameters of the alias's primary trait. This restriction was meant to avoid surprising errors: ```rust trait Foo = Copy; From c08f2b803c7f282b6ff64443b2e1a8e171147ae7 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 25 Oct 2023 17:31:18 -0400 Subject: [PATCH 15/20] Split one bullet into two --- text/3437-implementable-trait-alias.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 682c6cd7663..c9353d8040a 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -364,7 +364,8 @@ let _: IntIter = [1_u32].into_iter(); // `Item = u32` is redundant, (For example, `trait Foo = Bar + Send;` could be made implementable). - This may make the implementablility rules more intuitive to some, as the distinction between `+ Send` and `where Self: Send` would no longer be present. - However, it also might make the rules less intuitive, as the symmetry with `impl` blocks would be broken. - - Also, such a change might break the commutativity of `+`, or make it less obvious which trait is being implemented. + - Also, such a change might break the commutativity of `+`. + - It could also make it less obvious which trait is being implemented. - Again, user feedback could help make this decision. - Another option is to require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules more explicit, at the cost of cluttering user code and the attribute namespace. - A previous version of this RFC required generic parameters of implementable trait aliases to be used as generic parameters of the alias's primary trait. This restriction was meant to avoid surprising errors: From d0c32f37a04e3eb2c1a74bb8d9540e024552c494 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 25 Oct 2023 17:50:35 -0400 Subject: [PATCH 16/20] Alias associated items --- text/3437-implementable-trait-alias.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index c9353d8040a..0dccc1bf272 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -359,13 +359,14 @@ let _: IntIter = [1_u32].into_iter(); // `Item = u32` is redundant, # Rationale and alternatives - Very lightweight, with no new syntax forms. Compare "trait transformers" proposals, for example—they are generally much heavier. + - However, trait transformers would also address more use-cases (for example, sync and async versions of a trait). - Better ergonomics compared to purely proc-macro based solutions. - One alternative is to allow marker traits or auto traits to appear in `+` bounds of implementable aliases. (For example, `trait Foo = Bar + Send;` could be made implementable). - This may make the implementablility rules more intuitive to some, as the distinction between `+ Send` and `where Self: Send` would no longer be present. - However, it also might make the rules less intuitive, as the symmetry with `impl` blocks would be broken. - Also, such a change might break the commutativity of `+`. - - It could also make it less obvious which trait is being implemented. + - It could also make it less obvious which trait is being implemented, versus required; are we implementing `Bar`, `Send`, or both? - Again, user feedback could help make this decision. - Another option is to require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules more explicit, at the cost of cluttering user code and the attribute namespace. - A previous version of this RFC required generic parameters of implementable trait aliases to be used as generic parameters of the alias's primary trait. This restriction was meant to avoid surprising errors: @@ -456,3 +457,12 @@ Perhaps a more narrowly tailored version of this extension, in which both subtra - `trait Foo: Copy = Iterator;` could be allowed as an alternative to `trait Foo = Iterator where Self: Copy;`. - `impl Trait for Type { /* ... */ }` could be permitted in the future, to make the "copy-paste" rule of thumb work better. - The possible contents of `impl` bodies could be expanded, for example to support combining supertrait and subtrait implementations. +- Trait aliases could be expanded to support associated items of their own. For example: + +```rust +trait FutIter = Iterator { + type ItemOut = ::Output; +} +``` + +Type aliases might also want this capability. From 1167e58bc9f35d5a6dea44262169ba8cb6382144 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 26 Oct 2023 12:14:40 -0400 Subject: [PATCH 17/20] Clarify "weak"/"strong" --- text/3437-implementable-trait-alias.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 0dccc1bf272..2d07be0b819 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -9,7 +9,12 @@ Extend `#![feature(trait_alias)]` to permit `impl` blocks for trait aliases with # Motivation -Often, one desires to have a "weak" version of a trait, as well as a "strong" one providing additional guarantees. Subtrait relationships are commonly used for this, but they sometimes fall short—especially when the "strong" version is expected to see more use, or was stabilized first. +Often, one desires to have a "weak" version of a trait, as well as a "strong" one providing additional guarantees. Specifically, this RFC addresses trait relationships with the following properties: + +- For any implementation of the "strong" variant, there is exactly one way to implement the "weak" variant. +- For any implementation of the "weak" variant, there is at most one way to implement the "strong" variant. + +Subtrait relationships are commonly used to model this, but this often leads to coherence and backward compatibility issues—especially when the "strong" version is expected to see more use, or was stabilized first. ## Example: AFIT `Send` bound aliases @@ -121,7 +126,7 @@ error[E0404]: expected trait, found trait alias `Frobber` *This example relies on some language features that are currently pure speculation. Implementable trait aliases are potentially necessary to support this use-case, but not sufficient.* -Ever since the GAT MVP was stabilized, there has been discussion about how to add `LendingIterator` to the standard library, without breaking existing uses of `Iterator`. The relationship between `LendingIterator` and `Iterator` is "weak"/"strong"—an `Iterator` is a `LendingIterator` with some extra guarantees about the `Item` associated type. +Ever since the GAT MVP was stabilized, there has been discussion about how to add `LendingIterator` to the standard library, without breaking existing uses of `Iterator`. The relationship between `LendingIterator` and `Iterator` is "weak"/"strong"; an `Iterator` is a `LendingIterator` with an extra guarantee about its `Item` associated type (namely, that it is bivariant in its lifetime parameter). Now, let's imagine that Rust had some form of "variance bounds", that allowed restricting the way in which a type's GAT can depend on said GAT's generic parameters. One could then define `Iterator` in terms of `LendingIterator`, like so: From b027df1826e73ffa41e76eef36d17da42da779a3 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 29 Nov 2023 14:03:55 -0500 Subject: [PATCH 18/20] Future possibility: trait alias associated items --- text/3437-implementable-trait-alias.md | 328 ++++++++++++++++++++----- 1 file changed, 264 insertions(+), 64 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 2d07be0b819..c9d4d844ef6 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -5,22 +5,31 @@ # Summary -Extend `#![feature(trait_alias)]` to permit `impl` blocks for trait aliases with a single primary trait. Also support fully-qualified method call syntax with such aliases. +Extend `#![feature(trait_alias)]` to permit `impl` blocks for trait aliases with +a single primary trait. Also support fully-qualified method call syntax with +such aliases. # Motivation -Often, one desires to have a "weak" version of a trait, as well as a "strong" one providing additional guarantees. Specifically, this RFC addresses trait relationships with the following properties: +Often, one desires to have a "weak" version of a trait, as well as a "strong" +one providing additional guarantees. Specifically, this RFC addresses trait +relationships with the following properties: -- For any implementation of the "strong" variant, there is exactly one way to implement the "weak" variant. -- For any implementation of the "weak" variant, there is at most one way to implement the "strong" variant. +- For any implementation of the "strong" variant, there is exactly one way to + implement the "weak" variant. +- For any implementation of the "weak" variant, there is at most one way to + implement the "strong" variant. -Subtrait relationships are commonly used to model this, but this often leads to coherence and backward compatibility issues—especially when the "strong" version is expected to see more use, or was stabilized first. +Subtrait relationships are commonly used to model this, but this often leads to +coherence and backward compatibility issues—especially when the "strong" version +is expected to see more use, or was stabilized first. ## Example: AFIT `Send` bound aliases ### With subtraits -Imagine a library, `frob-lib`, that provides a trait with an async method. (Think `tower::Service`.) +Imagine a library, `frob-lib`, that provides a trait with an async method. +(Think `tower::Service`.) ```rust //! crate frob-lib @@ -29,9 +38,12 @@ pub trait Frobber { } ``` -Most of `frob-lib`'s users will need `Frobber::frob`'s return type to be `Send`, so the library wants to make this common case as painless as possible. But non-`Send` usage should be supported as well. +Most of `frob-lib`'s users will need `Frobber::frob`'s return type to be `Send`, +so the library wants to make this common case as painless as possible. But +non-`Send` usage should be supported as well. -`frob-lib`, following the recommended practice, decides to design its API in the following way: +`frob-lib`, following the recommended practice, decides to design its API in the +following way: ```rust //! crate frob-lib @@ -45,9 +57,13 @@ pub trait Frobber: LocalFrobber + Send {} impl Frobber for T where T: LocalFrobber + Send {} ``` -These two traits are, in a sense, one trait with two forms: the "weak" `LocalFrobber`, and "strong" `Frobber` that offers an additional `Send` guarantee. +These two traits are, in a sense, one trait with two forms: the "weak" +`LocalFrobber`, and "strong" `Frobber` that offers an additional `Send` +guarantee. -Because `Frobber` (with `Send` bound) is the common case, `frob-lib`'s documentation and examples put it front and center. So naturally, Joe User tries to implement `Frobber` for his own type. +Because `Frobber` (with `Send` bound) is the common case, `frob-lib`'s +documentation and examples put it front and center. So naturally, Joe User tries +to implement `Frobber` for his own type. ```rust //! crate joes-crate @@ -80,7 +96,8 @@ error[E0407]: method `frob` is not a member of trait `Frobber` | |_____^ not a member of trait `Frobber` ``` -Joe is confused. "What's a `LocalFrobber`? Isn't that only for non-`Send` use cases? Why do I need to care about all that?" But he eventually figures it out: +Joe is confused. "What's a `LocalFrobber`? Isn't that only for non-`Send` use +cases? Why do I need to care about all that?" But he eventually figures it out: ```rust //! crate joes-crate @@ -95,7 +112,8 @@ impl LocalFrobber for MyType { } ``` -This is distinctly worse; Joe now has to reference both `Frobber` and `LocalFrobber` in his code. +This is distinctly worse; Joe now has to reference both `Frobber` and +`LocalFrobber` in his code. ### With today's `#![feature(trait_alias)]` @@ -112,7 +130,8 @@ pub trait LocalFrobber { pub trait Frobber = LocalFrobber + Send; ``` -With today's `trait_alias`, it wouldn't make much difference for Joe. He would just get a slightly different error message: +With today's `trait_alias`, it wouldn't make much difference for Joe. He would +just get a slightly different error message: ``` error[E0404]: expected trait, found trait alias `Frobber` @@ -124,11 +143,21 @@ error[E0404]: expected trait, found trait alias `Frobber` ## Speculative example: GATification of `Iterator` -*This example relies on some language features that are currently pure speculation. Implementable trait aliases are potentially necessary to support this use-case, but not sufficient.* +*This example relies on some language features that are currently pure +speculation. Implementable trait aliases are potentially necessary to support +this use-case, but not sufficient.* -Ever since the GAT MVP was stabilized, there has been discussion about how to add `LendingIterator` to the standard library, without breaking existing uses of `Iterator`. The relationship between `LendingIterator` and `Iterator` is "weak"/"strong"; an `Iterator` is a `LendingIterator` with an extra guarantee about its `Item` associated type (namely, that it is bivariant in its lifetime parameter). +Ever since the GAT MVP was stabilized, there has been discussion about how to +add `LendingIterator` to the standard library, without breaking existing uses of +`Iterator`. The relationship between `LendingIterator` and `Iterator` is +"weak"/"strong"; an `Iterator` is a `LendingIterator` with an extra guarantee +about its `Item` associated type (namely, that it is bivariant in its lifetime +parameter). -Now, let's imagine that Rust had some form of "variance bounds", that allowed restricting the way in which a type's GAT can depend on said GAT's generic parameters. One could then define `Iterator` in terms of `LendingIterator`, like so: +Now, let's imagine that Rust had some form of "variance bounds", that allowed +restricting the way in which a type's GAT can depend on said GAT's generic +parameters. One could then define `Iterator` in terms of `LendingIterator`, like +so: ```rust //! core::iter @@ -146,17 +175,27 @@ where for<'a> Self::Item<'a>: bivariant_in<'a>; ``` -But, as with the previous example, we are foiled by the fact that trait aliases aren't `impl`ementable, so this change would break every `impl Iterator` block in existence. +But, as with the previous example, we are foiled by the fact that trait aliases +aren't `impl`ementable, so this change would break every `impl Iterator` block +in existence. ## Speculative example: `Async` trait -There has been some discussion about a variant of the `Future` trait with an `unsafe` poll method, to support structured concurrency ([here](https://rust-lang.github.io/wg-async/vision/roadmap/scopes/capability/variant_async_trait.html) for example). *If* such a change ever happens, then the same "weak"/"strong" relationship will arise: the safe-to-poll `Future` trait would be a "strong" version of the unsafe-to-poll `Async`. As the linked design notes explain, there are major problems with expressing that relationship in today's Rust. +There has been some discussion about a variant of the `Future` trait with an +`unsafe` poll method, to support structured concurrency ([here](https://rust-lang.github.io/wg-async/vision/roadmap/scopes/capability/variant_async_trait.html) +for example). *If* such a change ever happens, then the same "weak"/"strong" +relationship will arise: the safe-to-poll `Future` trait would be a "strong" +version of the unsafe-to-poll `Async`. As the linked design notes explain, there +are major problems with expressing that relationship in today's Rust. # Guide-level explanation -With `#![feature(trait_alias)]` (RFC #1733), one can define trait aliases, for use in bounds, trait objects, and `impl Trait`. This feature additionally allows writing `impl` blocks for a subset of trait aliases. +With `#![feature(trait_alias)]` (RFC #1733), one can define trait aliases, for +use in bounds, trait objects, and `impl Trait`. This feature additionally allows +writing `impl` blocks for a subset of trait aliases. -Let's rewrite our AFIT example from before, in terms of this feature. Here's what it looks like now: +Let's rewrite our AFIT example from before, in terms of this feature. Here's +what it looks like now: ```rust //! crate frob-lib @@ -189,7 +228,9 @@ impl Frobber for MyType { Joe's original code Just Works. -The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, paste it between the `for` and `{` of a trait `impl` block, and the result is syntactically valid—then the trait alias is most likely implementable. +The rule of thumb is: if you can copy everything between the `=` and `;` of a +trait alias, paste it between the `for` and `{` of a trait `impl` block, and the +result is syntactically valid—then the trait alias is most likely implementable. # Reference-level explanation @@ -197,19 +238,37 @@ The rule of thumb is: if you can copy everything between the `=` and `;` of a tr A trait alias has the following syntax (using the Rust Reference's notation): -> [Visibility](https://doc.rust-lang.org/stable/reference/visibility-and-privacy.html)? `trait` [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html) [GenericParams](https://doc.rust-lang.org/stable/reference/items/generics.html)? `=` [TypeParamBounds](https://doc.rust-lang.org/stable/reference/trait-bounds.html)? [WhereClause](https://doc.rust-lang.org/stable/reference/items/generics.html#where-clauses)? `;` +> [Visibility](https://doc.rust-lang.org/stable/reference/visibility-and-privacy.html)? +> `trait` [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html) +> [GenericParams](https://doc.rust-lang.org/stable/reference/items/generics.html)? +> `=` [TypeParamBounds](https://doc.rust-lang.org/stable/reference/trait-bounds.html)? +> [WhereClause](https://doc.rust-lang.org/stable/reference/items/generics.html#where-clauses)? +> `;` -For example, `trait Foo = PartialEq + Send where Self: Sync;` is a valid trait alias. +For example, `trait Foo = PartialEq + Send where Self: Sync;` is a valid +trait alias. Implementable trait aliases must follow a more restrictive form: -> [Visibility](https://doc.rust-lang.org/stable/reference/visibility-and-privacy.html)? `trait` [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html) [GenericParams](https://doc.rust-lang.org/stable/reference/items/generics.html)? `=` [TypePath](https://doc.rust-lang.org/stable/reference/paths.html#paths-in-types) [WhereClause](https://doc.rust-lang.org/stable/reference/items/generics.html#where-clauses)? `;` +> [Visibility](https://doc.rust-lang.org/stable/reference/visibility-and-privacy.html)? +> `trait` [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html) +> [GenericParams](https://doc.rust-lang.org/stable/reference/items/generics.html)? +> `=` [TypePath](https://doc.rust-lang.org/stable/reference/paths.html#paths-in-types) +> [WhereClause](https://doc.rust-lang.org/stable/reference/items/generics.html#where-clauses)? +> `;` -For example, `trait Foo = PartialEq where Self: Sync;` is a valid implementable alias. The `=` must be followed by a single trait (or implementable trait alias), and then some number of where clauses. The trait's generic parameter list may contain associated type constraints (for example `trait IntIterator = Iterator`). +For example, `trait Foo = PartialEq where Self: Sync;` is a valid +implementable alias. The `=` must be followed by a single trait (or +implementable trait alias), and then some number of where clauses. The trait's +generic parameter list may contain associated type constraints (for example +`trait IntIterator = Iterator`). ## Usage in `impl` blocks -An `impl` block for a trait alias looks just like an `impl` block for the underlying trait. The alias's where clauses are enforced as requirements that the `impl`ing type must meet—just like `where` clauses in trait declarations are treated. +An `impl` block for a trait alias looks just like an `impl` block for the +underlying trait. The alias's where clauses are enforced as requirements that +the `impl`ing type must meet—just like `where` clauses in trait declarations are +treated. ```rust pub trait CopyIterator = Iterator where Self: Send; @@ -235,6 +294,7 @@ trait Foo {} trait Bar = Foo where Self: Send; //impl Bar for T {} // ERROR: Need to add `T: Send` bound ``` + ```rust #![feature(trivial_bounds)] trait Foo {} @@ -252,7 +312,8 @@ trait Alias = Underlying; impl Alias<*const i32> for i32 {} // Error: `*const i32` is not `Send` ``` -If the trait alias uniquely constrains a portion of the `impl` block, that part can be omitted. +If the trait alias uniquely constrains a portion of the `impl` block, that part +can be omitted. ```rust pub trait IntIterator = Iterator where Self: Send; @@ -323,11 +384,16 @@ impl Frobber for MyType { } ``` -Trait aliases are `unsafe` to implement iff the underlying trait is marked `unsafe`. +Trait aliases are `unsafe` to implement iff the underlying trait is marked +`unsafe`. ## Usage in paths -Implementable trait aliases can also be used with trait-qualified and fully-qualified method call syntax, as well as in paths more generally. When used this way, they are treated equivalently to the underlying primary trait, with the additional restriction that all `where` clauses and type parameter/associated type bounds must be satisfied. +Implementable trait aliases can also be used with trait-qualified and +fully-qualified method call syntax, as well as in paths more generally. When +used this way, they are treated equivalently to the underlying primary trait, +with the additional restriction that all `where` clauses and type +parameter/associated type bounds must be satisfied. ```rust use std::array; @@ -344,7 +410,8 @@ let signed_iter = [1_i32].into_iter(); //IntIter::next(&mut signed_iter); // ERROR: Expected `::Item` to be `u32`, it is `i32` ``` -Implementable trait aliases can also be used with associated type bounds; the associated type must belong to the alias's primary trait. +Implementable trait aliases can also be used with associated type bounds; the +associated type must belong to the alias's primary trait. ```rust trait IteratorAlias = Iterator; @@ -357,24 +424,40 @@ let _: IntIter = [1_u32].into_iter(); // `Item = u32` is redundant, # Drawbacks -- The syntactic distance between implementable and non-implementable aliases is short, which might confuse users. In particular, the fact that `trait Foo = Bar + Send;` means something different than `trait Foo = Bar where Self: Send;` will likely be surprising to many. +- The syntactic distance between implementable and non-implementable aliases is + short, which might confuse users. In particular, the fact that + `trait Foo = Bar + Send;` means something different than + `trait Foo = Bar where Self: Send;` will likely be surprising to many. - Adds complexity to the language, which might surprise or confuse users. -- Many of the motivating use-cases involve language features that are not yet stable, or even merely speculative. More experience with those features might unearth better alternatives. +- Many of the motivating use-cases involve language features that are not yet + stable, or even merely speculative. More experience with those features might + unearth better alternatives. # Rationale and alternatives -- Very lightweight, with no new syntax forms. Compare "trait transformers" proposals, for example—they are generally much heavier. - - However, trait transformers would also address more use-cases (for example, sync and async versions of a trait). +- Very lightweight, with no new syntax forms. Compare "trait transformers" + proposals, for example—they are generally much heavier. + - However, trait transformers would also address more use-cases (for example, + sync and async versions of a trait). - Better ergonomics compared to purely proc-macro based solutions. -- One alternative is to allow marker traits or auto traits to appear in `+` bounds of implementable aliases. -(For example, `trait Foo = Bar + Send;` could be made implementable). - - This may make the implementablility rules more intuitive to some, as the distinction between `+ Send` and `where Self: Send` would no longer be present. - - However, it also might make the rules less intuitive, as the symmetry with `impl` blocks would be broken. +- One alternative is to allow marker traits or auto traits to appear in `+` + bounds of implementable aliases. (For example, `trait Foo = Bar + Send;` could + be made implementable). + - This may make the implementablility rules more intuitive to some, as the + distinction between `+ Send` and `where Self: Send` would no longer be + present. + - However, it also might make the rules less intuitive, as the symmetry with + `impl` blocks would be broken. - Also, such a change might break the commutativity of `+`. - - It could also make it less obvious which trait is being implemented, versus required; are we implementing `Bar`, `Send`, or both? + - It could also make it less obvious which trait is being implemented, versus + required; are we implementing `Bar`, `Send`, or both? - Again, user feedback could help make this decision. -- Another option is to require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules more explicit, at the cost of cluttering user code and the attribute namespace. -- A previous version of this RFC required generic parameters of implementable trait aliases to be used as generic parameters of the alias's primary trait. This restriction was meant to avoid surprising errors: +- Another option is to require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. + This would make the otherwise-subtle implementability rules more explicit, at + the cost of cluttering user code and the attribute namespace. +- A previous version of this RFC required generic parameters of implementable + trait aliases to be used as generic parameters of the alias's primary trait. + This restriction was meant to avoid surprising errors: ```rust trait Foo = Copy; @@ -403,11 +486,35 @@ impl Foo for MyType { // ERROR: overlapping impls } ``` -However, upon further discussion, I now lean toward allowing more flexibility, even at the risk of potential confusion. +However, upon further discussion, I now lean toward allowing more flexibility, +even at the risk of potential confusion. + +# Prior art + +- [`trait_transformer` macro](https://github.com/google/impl_trait_utils) + +# Unresolved questions + +- How does `rustdoc` render these? Consider the `Frobber` example—ideally, + `Frobber` should be emphasized compared to `LocalFrobber`, but it's not clear + how that would work. + +# Future possibilities -## What about combining multiple primary traits, and their items, into one `impl` block? +- New kinds of bounds: anything that makes `where` clauses more powerful would + make this feature more powerful as well. + - Variance bounds would allow this feature to support backward-compatible + GATification. + - Method unsafety bounds would support the `Future` → `Async` use-case. +- `trait Foo: Copy = Iterator;` could be allowed as an alternative syntax to + `trait Foo = Iterator where Self: Copy;`. +- `impl Trait for Type { /* ... */ }` could be permitted in the + future, to make the "copy-paste" rule of thumb work better. -It's possible to imagine an extension of this proposal, that allows trait aliases to be implementable even if they have multiple primary traits. For example: +## Combining multiple primary traits into one `impl` block + +As an extension of this proposal, Rust could allows trait aliases to be +implementable even if they have multiple primary traits. For example: ```rust trait Foo = Clone + PartialEq; @@ -425,9 +532,10 @@ impl Foo for Stu { } ``` -Such a feature could be useful when a trait has multiple items and you want to split it in two. +Such a feature could be useful when a trait has multiple items and you want to +split it in two. -However, there are some issues. Most glaring is the risk of name collisions: +However, there are some issues to resolve. Most glaring is the risk of name collisions: ```rust trait A { @@ -442,32 +550,124 @@ trait B { trait C = A + B; ``` -Such a feature could also make it harder to find the declaration of a trait item from its implementation, especially if IDE "go to definition" is not available. One would need to first find the trait alias definition, and then look through every primary trait to find the item. (However, given the current situation with postfix method call syntax, maybe this is an acceptable trade-off.) +Such a feature could also make it harder to find the declaration of a trait item +from its implementation, especially if IDE "go to definition" is not available. +One would need to first find the trait alias definition, and then look through +every primary trait to find the item. (However, given the current situation with +postfix method call syntax, maybe this is an acceptable trade-off.) -Perhaps a more narrowly tailored version of this extension, in which both subtrait and supertrait explicitly opt-in to support sharing an `impl` block with one another, would satisfy the backward-compatibility use-case while avoiding the above issues. I think exploring that is best left to a future RFC. +Perhaps a more narrowly tailored version of this extension, in which both +subtrait and supertrait explicitly opt-in to support sharing an `impl` block +with one another, would satisfy the backward-compatibility use-case while +avoiding the above issues. Alternatively, there could be an explictit syntax for +disambiguating within the `impl` block which trait an item comes from. -# Prior art +## Associated items in trait aliases -- [`trait_transformer` macro](https://github.com/google/impl_trait_utils) +Trait aliases could be expanded to support associated types and consts[^1] that +are uniquely constrained by the associated items of the underlying trait and +where clauses. For example, imagine that `foolib v1.0` defines a trait like the +following: -# Unresolved questions +[^1]: Supporting associated methods (with non-overridable defaults) is also a +possibility. However, extension traits already address all potential use-cases +of that feature (as far as I can see). -- How does `rustdoc` render these? Consider the `Frobber` example—ideally, `Frobber` should be emphasized compared to `LocalFrobber`, but it's not clear how that would work. +```rust +//! foolib 1.0 -# Future possibilities +pub trait Frobnicate { + type Item; + + frob(&self) -> Option; +} +``` + +Later on, `foolib`'s developers realize that many users want their `frob()` +implementation to return something other than `Option` (`Result`, for example). +With trait alias associated types, this could be done backward-compatibly and +with no coherence issues: -- New kinds of bounds: anything that makes `where` clauses more powerful would make this feature more powerful as well. - - Variance bounds would allow this feature to support backward-compatible GATification. - - Method unsafety bounds would support the `Future` → `Async` use-case. -- `trait Foo: Copy = Iterator;` could be allowed as an alternative to `trait Foo = Iterator where Self: Copy;`. -- `impl Trait for Type { /* ... */ }` could be permitted in the future, to make the "copy-paste" rule of thumb work better. -- The possible contents of `impl` bodies could be expanded, for example to support combining supertrait and subtrait implementations. -- Trait aliases could be expanded to support associated items of their own. For example: - ```rust -trait FutIter = Iterator { - type ItemOut = ::Output; +//! foolib 1.1 + +pub trait FlexibleFrobnicate { + type Output; + + frob(&self) -> Self::Output; +} + +pub trait Frobnicate = FlexibleFrobnicate> { + type Item; } ``` -Type aliases might also want this capability. +`impl` blocks should be allowed to omit associated items that are +"uniquely constrained" by other such items. Such a capability would be useful +even outside the context of trait aliases, for example when implementing +`IntoIterator`: + +```rust +struct Iter; + +impl Iterator for Iter { + type Item = u32; + + fn next(&mut self) -> Option { + Some(42) + } +} + +struct IntoIter; + +impl IntoIterator for IntoIter { + type IntoIter = Iter; + + // `type Item` is uniquely constrained by `Self::IntoIter`, + // so it could be omitted. + + fn into_iter(self) -> Iter { + Iter + } +} +``` + +### Name conflicts + +One wrinkle with the above scheme, is that it is possible for the trait being +aliased to define, in a new minor version, additional trait items that have the +same name as associated items of the alias itself. + +```rust +//! foolib +#![feature(associated_type_defaults)] + +pub trait Foo { + type Assoc; + /// Added in v1.1 + type WrappedAssoc = Result; +} +``` + +```rust +//! aliaslib +extern crate foolib; + +pub trait Alias = foolib::Foo { + /// Added in v1.1 + type WrappedAssoc = Option; +} +``` + +```rust +//! thirdlib +extern crate foolib; +extern crate aliaslib; + +impl Alias for Bar { + // What does this do? + // The issues here are similar to those for combining multiple traits + // in the same `impl` block. + type WrappedAssoc = Option<()>; +} +``` From 601cc9c0d9ecc497a295104f0ec2ee2cac4a6a2b Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sat, 9 Dec 2023 15:00:38 -0500 Subject: [PATCH 19/20] Future possibility: trait aliases constrained by their associated items --- text/3437-implementable-trait-alias.md | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index c9d4d844ef6..3ed229ee82b 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -632,6 +632,54 @@ impl IntoIterator for IntoIter { } ``` +### Trait aliases constrained by their associated items + +If trait aliases with associated items are additionally allowed to refer to +those items from the definition of the alias itself, it would be possible to +express certain kinds of trait bounds that current `where` clauses do not +support. + +For example: + +```rust +/// An `Iterator` that yields `Result`s. +trait ResultIterator = Iterator> { + type Ok; + type Err; +} +``` + +In the context of the above example, a `T: ResultIterator` bound would mean +"there exist unique types `Ok` and `Err` such that +`T: Iterator>` holds". Current Rust provides no mechanism +for expressing a bound like that; you need a separate trait, like [`TryFuture`](https://docs.rs/futures-core/latest/futures_core/future/trait.TryFuture.html). + +This feature could even allow GATification of `Iterator` (or `FnMut`, etc) +without variance bounds: + +```rust +pub trait LendingIterator { + type LentItem<'a> + where + Self: 'a; + + fn next<'a>(&'a mut self) -> Option>; +} + +// `T: Iterator` means +// "there exists a unique type `Item` such that +// `T: LendingIterator where for<'a> Self::LentItem<'a> = Item`" +// (which holds iff `Self::LentItem<'a>` is bivariant in `'a`). +pub trait Iterator = LendingIterator +where + // Still need to solve implied `'static` bound problem + // (https://blog.rust-lang.org/2022/10/28/gats-stabilization.html#implied-static-requirement-from-higher-ranked-trait-bounds) + for<'a> Self::LentItem<'a> = Self::Item, +{ + type Item; +} +``` + ### Name conflicts One wrinkle with the above scheme, is that it is possible for the trait being From 24f15c1b17a214eb44b406d3be9cfc6762993d30 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Tue, 19 Dec 2023 15:44:18 -0500 Subject: [PATCH 20/20] Rewrite AFIT example in terms of `trait-variant` --- text/3437-implementable-trait-alias.md | 218 ++++++++++++++----------- 1 file changed, 120 insertions(+), 98 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 3ed229ee82b..25f949471a5 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -21,127 +21,154 @@ relationships with the following properties: implement the "strong" variant. Subtrait relationships are commonly used to model this, but this often leads to -coherence and backward compatibility issues—especially when the "strong" version -is expected to see more use, or was stabilized first. +coherence and backward compatibility issues. -## Example: AFIT `Send` bound aliases - -### With subtraits +## AFIT `Send` bound aliases Imagine a library, `frob-lib`, that provides a trait with an async method. -(Think `tower::Service`.) ```rust -//! crate frob-lib -pub trait Frobber { +//! crate `frob-lib` +pub trait Frob { async fn frob(&self); } ``` -Most of `frob-lib`'s users will need `Frobber::frob`'s return type to be `Send`, +Most of `frob-lib`'s users will need `Frob::frob`'s return type to be `Send`, so the library wants to make this common case as painless as possible. But non-`Send` usage should be supported as well. -`frob-lib`, following the recommended practice, decides to design its API in the -following way: +### MVP: `trait_variant` + +Because Return Type Notation isn't supported yet, `frob-lib` follows the +recommended practice of using the [`trait-variant`](https://docs.rs/trait-variant/) +crate to have `Send` and non-`Send` variants. ```rust -//! crate frob-lib +//! crate `frob-lib`` -pub trait LocalFrobber { - async fn frob(&self); +#[trait_variant::make(Frob: Send)] +pub trait LocalFrob { + async fn frob(&mut self); } - -// or whatever RTN syntax is decided on -pub trait Frobber: LocalFrobber + Send {} -impl Frobber for T where T: LocalFrobber + Send {} ``` -These two traits are, in a sense, one trait with two forms: the "weak" -`LocalFrobber`, and "strong" `Frobber` that offers an additional `Send` -guarantee. - -Because `Frobber` (with `Send` bound) is the common case, `frob-lib`'s -documentation and examples put it front and center. So naturally, Joe User tries -to implement `Frobber` for his own type. +However, this API has limitations. Fox example, `frob-lib` may want to offer a +`DoubleFrob` wrapper: ```rust -//! crate joes-crate -use frob_lib::Frobber; +pub struct DoubleFrob(T); -struct MyType; +impl LocalFrob for DoubleFrob { + async fn frob(&mut self) { + self.0.frob().await; + self.0.frob().await; + } +} +``` + +As written, this wrapper only implements `LocalFrob`, which means that it's not +fully compatible with work-stealing executors. So `frob-lib` tries to add a +`Frob` implementation as well: -impl Frobber for MyType { - async fn frob(&self) { - println!("Sloo is 120% klutzed. Initiating brop sequence...") +```rust +impl Frob for DoubleFrob { + async fn frob(&mut self) { + self.0.frob().await; + self.0.frob().await; } } ``` -But one `cargo check` later, Joe is greeted with: +Coherence, however, rejects this. ``` -error[E0277]: the trait bound `MyType: LocalFrobber` is not satisfied - --> src/lib.rs:6:18 - | -6 | impl Frobber for MyType { - | ^^^^^^ the trait `LocalFrobber` is not implemented for `MyType` +error[E0119]: conflicting implementations of trait `LocalFrob` for type `DoubleFrob<_>` + --> src/lib.rs:1:1 + | +1 | #[trait_variant::make(Frob: Send)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `DoubleFrob<_>` +... +8 | impl LocalFrob for DoubleFrob { + | ---------------------------------------------- first implementation here + | + = note: this error originates in the attribute macro `trait_variant::make` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0407]: method `frob` is not a member of trait `Frobber` - --> src/lib.rs:7:5 - | -7 | / async fn frob(&self) { -8 | | println!("Sloo is 120% klutzed. Initiating brop sequence...") -9 | | } - | |_____^ not a member of trait `Frobber` +For more information about this error, try `rustc --explain E0119`. ``` -Joe is confused. "What's a `LocalFrobber`? Isn't that only for non-`Send` use -cases? Why do I need to care about all that?" But he eventually figures it out: +With the `trait_variant`-based design, it's impossible to support both `Send` +and non-`Send` usage in the same `DoubleFrob` type. + +### Migrating to Return Type Notation + +A few Rust releases later, Return Type Notation is stabilized. `frob-lib` wants +to migrate to it in order to address the issues with the `trait_variant` +solution: ```rust -//! crate joes-crate -use frob_lib::LocalFrobber; +//! crate `frob-lib`` + +pub trait LocalFrob { + async fn frob(&self); +} + +// or whatever RTN syntax is decided on +pub trait Frob: LocalFrob + Send {} +impl Frob for T where T: LocalFrob + Send {} +``` + +However, this is an incompatible change; all implementations of `Frob` are +broken! + +```rust +//! crate `downstream` +use frob_lib::Frob; struct MyType; -impl LocalFrobber for MyType { - async fn frob(&self) { - println!("Sloo is 120% klutzed. Initiating brop sequence...") - } +impl Frob for MyType { + // Now an error, "trait `Frob` has no method `frob`" + async fn frob(&self) { /* ... */ } +} +``` + +All `impl` blocks for `Frob` must be migrated to reference `LocalFrob` instead. + +```rust +//! crate `downstream` +use frob_lib::LocalFrob; + +struct MyType; + +impl LocalFrob for MyType { + async fn frob(&self) { /* ... */ } } ``` -This is distinctly worse; Joe now has to reference both `Frobber` and -`LocalFrobber` in his code. +Not only is this change disruptive, it also results in more confusing code. +`downstream` is written for work-stealing executors, but needs to reference +`LocalFrob` anyway. ### With today's `#![feature(trait_alias)]` What if `frob-lib` looked like this instead? ```rust -//! crate frob-lib +//! crate `frob-lib` #![feature(trait_alias)] -pub trait LocalFrobber { +pub trait LocalFrob { async fn frob(&self); } -pub trait Frobber = LocalFrobber + Send; +pub trait Frob = LocalFrob + Send; ``` -With today's `trait_alias`, it wouldn't make much difference for Joe. He would -just get a slightly different error message: +With today's `trait_alias`, it wouldn't make much difference for `downstream`. +`impl` blocks for `Frob` would still be broken. -``` -error[E0404]: expected trait, found trait alias `Frobber` - --> src/lib.rs:6:6 - | -6 | impl Frobber for MyType { - | ^^^^^^^ not a trait -``` - -## Speculative example: GATification of `Iterator` +## (Speculative) GATification of `Iterator` *This example relies on some language features that are currently pure speculation. Implementable trait aliases are potentially necessary to support @@ -160,7 +187,7 @@ parameters. One could then define `Iterator` in terms of `LendingIterator`, like so: ```rust -//! core::iter +//! `core::iter` pub trait LendingIterator { type Item<'a> where @@ -179,14 +206,14 @@ But, as with the previous example, we are foiled by the fact that trait aliases aren't `impl`ementable, so this change would break every `impl Iterator` block in existence. -## Speculative example: `Async` trait +## (Speculative) `Async` trait There has been some discussion about a variant of the `Future` trait with an -`unsafe` poll method, to support structured concurrency ([here](https://rust-lang.github.io/wg-async/vision/roadmap/scopes/capability/variant_async_trait.html) -for example). *If* such a change ever happens, then the same "weak"/"strong" -relationship will arise: the safe-to-poll `Future` trait would be a "strong" -version of the unsafe-to-poll `Async`. As the linked design notes explain, there -are major problems with expressing that relationship in today's Rust. +`unsafe` poll method, to support structured concurrency ([wg-async design notes](https://rust-lang.github.io/wg-async/vision/roadmap/scopes/capability/variant_async_trait.html)). +*If* such a change ever happens, then the same "weak"/"strong" relationship will +arise: the safe-to-poll `Future` trait would be a "strong" version of the +unsafe-to-poll `Async`. As the linked design notes explain, there are major +problems with expressing that relationship in today's Rust. # Guide-level explanation @@ -194,43 +221,41 @@ With `#![feature(trait_alias)]` (RFC #1733), one can define trait aliases, for use in bounds, trait objects, and `impl Trait`. This feature additionally allows writing `impl` blocks for a subset of trait aliases. -Let's rewrite our AFIT example from before, in terms of this feature. Here's -what it looks like now: +Let's rewrite our AFIT example from before using this feature. Here's what it +looks like now: ```rust -//! crate frob-lib +//! crate `frob-lib` #![feature(trait_alias)] -pub trait LocalFrobber { +pub trait LocalFrob { async fn frob(&self); } -pub trait Frobber = LocalFrobber +pub trait Frob = LocalFrob where // not `+ Send`! Self: Send; ``` ```rust -//! crate joes-crate +//! crate `downstream` #![feature(trait_alias_impl)] -use frob_lib::Frobber; +use frob_lib::Frob; struct MyType; -impl Frobber for MyType { - async fn frob(&self) { - println!("Sloo is 120% klutzed. Initiating brop sequence...") - } +impl Frob for MyType { + async fn frob(&self) { /* ... */ } } ``` -Joe's original code Just Works. +`impl`s of `Frob` now Just Work. The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, paste it between the `for` and `{` of a trait `impl` block, and the -result is syntactically valid—then the trait alias is most likely implementable. +result is syntactically valid—then the trait alias is implementable. # Reference-level explanation @@ -323,7 +348,6 @@ struct Baz; impl IntIterator for Baz { // The alias constrains `Self::Item` to `i32`, so we don't need to specify it // (though we are allowed to do so if desired). - // type Item = i32; fn next(&mut self) -> i32 { -27 @@ -359,28 +383,26 @@ Alias `impl`s also allow omitting implied `#[refine]`s: //! crate frob-lib #![feature(trait_alias)] -pub trait LocalFrobber { +pub trait LocalFrob { async fn frob(&self); } // not `+ Send`! -pub trait Frobber = LocalFrobber where Self: Send; +pub trait Frob = LocalFrob where Self: Send; ``` ```rust //! crate joes-crate #![feature(trait_alias_impl)] -use frob_lib::Frobber; +use frob_lib::Frob; struct MyType; -impl Frobber for MyType { +impl Frob for MyType { // The return future of this method is implicitly `Send`, as implied by the alias. // No `#[refine]` is necessary. - async fn frob(&self) { - println!("Sloo is 120% klutzed. Initiating brop sequence...") - } + async fn frob(&self) { /* ... */ } } ``` @@ -495,8 +517,8 @@ even at the risk of potential confusion. # Unresolved questions -- How does `rustdoc` render these? Consider the `Frobber` example—ideally, - `Frobber` should be emphasized compared to `LocalFrobber`, but it's not clear +- How does `rustdoc` render these? Consider the `Frob` example—ideally, + `Frob` should be emphasized compared to `LocalFrob`, but it's not clear how that would work. # Future possibilities