From 5ef6311c96b8502afd144957e98bbfa1bb117579 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 24 May 2023 19:35:18 -0400 Subject: [PATCH 01/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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 From ef13af9a39019665c59209e04c7d92853535af61 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Mon, 7 Apr 2025 17:45:00 -0400 Subject: [PATCH 21/45] Reflow --- text/3437-implementable-trait-alias.md | 60 +++++++++++++++----------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 25f949471a5..4a81b9c4bb3 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -34,15 +34,16 @@ pub trait Frob { } ``` -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 +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. ### 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. +recommended practice of using the +[`trait-variant`](https://docs.rs/trait-variant/) crate to have `Send` and +non-`Send` variants. ```rust //! crate `frob-lib`` @@ -209,7 +210,8 @@ in existence. ## (Speculative) `Async` trait There has been some discussion about a variant of the `Future` trait with an -`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)). +`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 @@ -264,9 +266,11 @@ result is syntactically valid—then the trait alias is implementable. 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) +> `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)? +> `=` +> [TypeParamBounds](https://doc.rust-lang.org/stable/reference/trait-bounds.html)? > [WhereClause](https://doc.rust-lang.org/stable/reference/items/generics.html#where-clauses)? > `;` @@ -276,9 +280,11 @@ 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) +> `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) +> `=` +> [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)? > `;` @@ -447,9 +453,9 @@ 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. + 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 @@ -474,9 +480,10 @@ let _: IntIter = [1_u32].into_iter(); // `Item = u32` is redundant, - 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. +- 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: @@ -517,9 +524,9 @@ even at the risk of potential confusion. # Unresolved questions -- 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. +- 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 @@ -557,7 +564,8 @@ impl Foo for Stu { 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 to resolve. 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 { @@ -624,10 +632,9 @@ pub trait Frobnicate = FlexibleFrobnicate> { } ``` -`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`: +`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; @@ -672,9 +679,10 @@ trait ResultIterator = Iterator> { ``` 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). +"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: From e5c19f0954b1c335a06bd7a4286c5d48d0154b8a Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 10 Apr 2025 15:22:59 -0400 Subject: [PATCH 22/45] Major extension - Allow implementing aliases for multiple traits - Introduce trait alias bodies --- text/3437-implementable-trait-alias.md | 688 ++++++++++++++----------- 1 file changed, 398 insertions(+), 290 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 4a81b9c4bb3..a283de9e576 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -5,9 +5,11 @@ # 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 most trait aliases. +Also support fully-qualified method call syntax with such aliases. + +Additionally, allow trait aliases to have bodies, which can contain `type`s, +`const`s, and.or `fn`s. # Motivation @@ -23,6 +25,11 @@ relationships with the following properties: Subtrait relationships are commonly used to model this, but this often leads to coherence and backward compatibility issues. +In addition, sometimes one may wish to split a trait into two parts; however, +this is impossible to accomplish backward-compatibly at present. + +It is also impossible to rename trait items in a backward-compatible way. + ## AFIT `Send` bound aliases Imagine a library, `frob-lib`, that provides a trait with an async method. @@ -46,7 +53,7 @@ recommended practice of using the non-`Send` variants. ```rust -//! crate `frob-lib`` +//! crate `frob-lib` #[trait_variant::make(Frob: Send)] pub trait LocalFrob { @@ -169,56 +176,57 @@ pub trait Frob = LocalFrob + Send; With today's `trait_alias`, it wouldn't make much difference for `downstream`. `impl` blocks for `Frob` would still be broken. -## (Speculative) GATification of `Iterator` +## Splitting a trait -*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.* +Consider this humble library: -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). +```rust +trait Frob { + fn frazzle(&self); + fn brop(&self); +} +``` + +Now, let us imagine that the authors of `frob-lib` realize that some types can +only implement one of `frazzle` or `brop`, but not both. They want to split the +trait in two. But, as with our previous example, there is no way to do this in a +backward-compatible way without painful compromises. + +## Removing a `Sized` bound -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: +Consider this other humble library: ```rust -//! `core::iter` -pub trait LendingIterator { - type Item<'a> - where - Self: 'a; +trait Frob { + type Frobber: - fn next(&'a mut self) -> Self::Item<'a>; + fn frob(&self, frobber: &Self::Frobber); } - -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. +Currently, `Frob::Frobber` has a `Sized` bound, but the signature of `frob()` +doesn't require it. However, there is no good way at present for `frob-lib` to +remove the bound. -## (Speculative) `Async` trait +## Renaming trait items -There has been some discussion about a variant of the `Future` trait with an -`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. +Consider this verbose library: + +```rust +trait TraitForFrobbing { + type TypeThatEnablesFrobbing: + + fn perform_the_frobbing_operation_posthaste(&self, frobber: &Self::TypeThatEnablesFrobbing); +} +``` + +The library author may want to rename the trait and its items to something less +unwieldy. Unfortunately, he has no good way to accomplish this at present. # Guide-level explanation +## `impl` blocks for 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. @@ -255,51 +263,106 @@ impl Frob for MyType { `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 implementable. +## Bodies for trait aliases -# Reference-level explanation +Trait aliases can also now sepcify an optional body, which can contain various +items. These items are themselves aliases for items defined on the respective +traits. + +```rust +//! crate `foolib` + +trait Foo { + type AssocTy; + + const ASSOC: i32; -## Implementability rules + fn method(&self); + fn another_method(&self); +} -A trait alias has the following syntax (using the Rust Reference's notation): +trait QuiteVerboseAlias = Foo { + type TypeThatIsAssociated = Self::AssocTy; -> [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)? -> `;` + const ASSOCIATED_CONSTANT: i32 = Self::ASSOC; -For example, `trait Foo = PartialEq + Send where Self: Sync;` is a valid -trait alias. + fn a_method_you_can_call = Self::method; +} +``` -Implementable trait aliases must follow a more restrictive form: +You can then refer to these associated items wherever the alias is in +scope: -> [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)? -> `;` +```rust +fn do_thing(arg: T, another_arg: T::TypeThatIsAssociated) { + arg.a_method_you_can_call(); -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`). + // You can also still use the original names from the aliased trait + arg.method(); + arg.another_method(); +} +``` -## Usage in `impl` blocks +You can also use the alias names when implementing the trait alias: -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 +impl QuiteVerboseAlias for () { + type TypeThatIsAssociated = i32; + + const ASSOC: i32 = 42; + + fn a_method_you_can_call(&self) { + println!("foo") + } + + fn another_method(&self) { + println!("bar") + } +} +``` + +## Implementing trait aliases for multiple traits + +Trait aliases that combine multiple traits with `+` are also implementable: + +```rust +trait Foo { + fn foo(); +} + +trait Bar { + fn bar(); +} + +trait FooBar = Foo + Bar; + +impl FooBar for () { + fn foo() { + println!("foo"); + } + + fn bar() { + println!("bar"); + } +} +``` + +However, be careful: if both traits have an item of the same name, you won’t be +able to disambiguate, and will have to split the `impl` block into separate +impls for the two underlying traits. Or, alternatively, you can give the trait +alias a body, and define item aliases with distinct names for each of the +conflicting items. + +# Reference-level explanation + +## Implementing trait aliases + +A trait alias is considered implementable if it includes at least one trait +reference before the `where` keyword. (Henceforth, these are the “primary +traits” of the alias.)`impl`ementing the alias implements these primary traits, +and only these traits. 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; @@ -412,16 +475,16 @@ impl Frob for MyType { } ``` -Trait aliases are `unsafe` to implement iff the underlying trait is marked -`unsafe`. +Trait aliases are `unsafe` to implement iff one or more primary traits are +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. +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(s), with the additional +restriction that all `where` clauses and type parameter/associated type bounds +must be satisfied. ```rust use std::array; @@ -438,8 +501,7 @@ 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. ```rust trait IteratorAlias = Iterator; @@ -450,302 +512,348 @@ let _: IntIter = [1_u32].into_iter(); // `Item = u32` is redundant, //let _: IntIter = [1.0_f64].into_iter(); // ERROR: `Item = f64` conflicts with `Item = u32` ``` -# 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. -- 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. - - 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, 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: +Items from traits in `where` clauses of the alias are accessible, unless +shadowed by items in the primary trait(s): ```rust -trait Foo = Copy; +trait Foo { + fn frob(); + fn frit(); +} -#[derive(Clone)] -struct MyType; +trait Bar { + fn frit(); + fn bork(); +} -impl Foo for MyType {} // ERROR: `T`` is unconstrained +trait FooBar = Bar where Self: Foo; + +fn example() { + T::frob(); // resolves to `::frob` + T::frit(); // resolves to `::frit` + T::bork(); // resolves to `::bork` +} ``` -```rust -trait Foo = Iterator; +## Aliases with multiple primary traits -struct MyType; +A trait alias with multiple primary traits can be implemented, unless one of the +primary traits requires specifying an item that conflicts with an item of the +same name in a different primary trait. -impl Foo for MyType { - fn next(&mut Self) -> Option { - todo!() - } +```rust +trait Foo { + fn frob(); } -impl Foo for MyType { // ERROR: overlapping impls - fn next(&mut Self) -> Option { - todo!() - } +trait Bar { + fn frob() {} } -``` -However, upon further discussion, I now lean toward allowing more flexibility, -even at the risk of potential confusion. +// This isn't implementable, due to conflict between `Foo::frob` and `Bar::frob` +trait FooBar = Foo + Bar; +``` -# Prior art +If the confliting items all have defaults, the alias will be implementable, but +overriding the defaults will not be possible. -- [`trait_transformer` macro](https://github.com/google/impl_trait_utils) +```rust +trait Foo { + fn frob() {} +} -# Unresolved questions +trait Bar { + fn frob() {} +} -- 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. +// This is implementable, but the `impl` block won't be able +// to override the default bodies of the `frob()` functions. +trait FooBar = Foo + Bar; +``` -# Future possibilities +Name conflicts of this sort also cause ambiguity when using the alias: -- 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. +```rust +fn example() { + T::frob(); // ERROR: ambiguous +} +``` -## Combining multiple primary traits into one `impl` block +To resolve these conflicts, you can use trait alias bodies, as described below. -As an extension of this proposal, Rust could allows trait aliases to be -implementable even if they have multiple primary traits. For example: +## Bodies for trait aliases -```rust -trait Foo = Clone + PartialEq; +Trait aliases can now optionally contain a body, specifying aliases for various +items. These can be types, constants, or functions. -struct Stu; +### `type`s and `const` items in trait alias bodies -impl Foo for Stu { - fn clone(&self) -> Self { - Stu - } +```rust +trait Foo { + type Assoc; + const ASSOC: i32; +} - fn eq(&self, other: &Self) -> bool { - true - } +trait Alias = Foo { + type AssocVec = Vec; + const ASSOC_PLUS_1: i32 = Self::ASSOC + 1; } ``` -Such a feature could be useful when a trait has multiple items and you want to -split it in two. +`::AssocVec` means the same thing as `Vec<::Assoc>`, and +`::ASSOC_PLUS_1` is equivalent to `const { ::ASSOC + 1 }`. -However, there are some issues to resolve. Most glaring is the risk of name -collisions: +To be implementable, a `type` or `const` alias item must obey certain +restrictions. It must either be set equal to an item of a primary trait of the +alias: ```rust -trait A { - fn foo(); +trait Foo { + type Assoc; + const ASSOC: i32; } -trait B { - fn foo(); +trait Alias = Foo { + type Associated = Self::Assoc; + const ASSOCIATED: i32 = Self::ASSOC; } -// How would you write an `impl` block for this? -trait C = A + B; +impl Alias for () { + type Associated = i32; // Equivalent to `type Assoc = i32;` + const ASSOCIATED: i32 = 42; // Equivalent to `const ASSOC: i32 = 42;` +} ``` -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. Alternatively, there could be an explictit syntax for -disambiguating within the `impl` block which trait an item comes from. +Or, the trait alias must set an associated type of the primary trait equal to a +generic type, with the alias item as a generic parameter of that type. For +example, here is +[`TryFuture`](https://docs.rs/futures-core/latest/futures_core/future/trait.TryFuture.html) +as an implementable trait alias: -## Associated items in trait aliases - -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: +```rust +/// This means: +/// "A `TryFuture` is a `Future` where there exist +/// unique types `Self::Ok` and `Self::Error` such that +/// `Self: Future>`." +pub trait TryFuture = Future> { + // The values of these `type`s are defined by the `Output = ...` above. + // So there is no need for `= ...` RHS + type Ok; + type Error; +} -[^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). +// Example impl -```rust -//! foolib 1.0 +struct AlwaysFails; -pub trait Frobnicate { - type Item; +impl TryFuture for AlwaysFails { + type Ok = !; + type Error = (); - frob(&self) -> Option; + fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Err(())) + } } ``` -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: +The generic parameter can also be nested: ```rust -//! foolib 1.1 - -pub trait FlexibleFrobnicate { - type Output; - - frob(&self) -> Self::Output; +trait Foo { + type Assoc; } -pub trait Frobnicate = FlexibleFrobnicate> { - type Item; +trait Bar = Foo>> { + type Foo; } ``` -`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`: +Items defined in a trait alias body shadow items of the same name in a primary +trait. ```rust -struct Iter; +trait Foo { + type Assoc; +} -impl Iterator for Iter { - type Item = u32; +trait Bar { + type Assoc; +} - fn next(&mut self) -> Option { - Some(42) - } +trait FooBar = Foo + Bar { + type Assoc = ::Assoc; // `FooBar::Assoc` will resolve to `Foo::Assoc` + type BarAssoc = ::Assoc; } +``` -struct IntoIter; +#### GATs in type alias bodies -impl IntoIterator for IntoIter { - type IntoIter = Iter; +Type alias bodies can also contain GATs. These are also subject to the +implementability rules, though reordering generic parameters does not inhibit +implementability. - // `type Item` is uniquely constrained by `Self::IntoIter`, - // so it could be omitted. +```rust +trait Foo { + type Gat<'a, T, U> + where + Self: 'a; +} - fn into_iter(self) -> Iter { - Iter - } +trait Alias = Foo { + type Tag<'a, U, T> = Self::Gat<'a, T, U>; // Implementable + type GatVec<'a, T, U> = Self::Gat<'a, Vec, U>; // Not implementable + type GatSame<'a, T> = Self::Gat<'a, T, T>; // Not implementable } ``` -### Trait aliases constrained by their associated items +### `fn`s in type alias bodies -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. +#### Implementable `fn`s -For example: +Trait alias bodies can also contain aliases for methods of its primary trait(s). +This involves a new syntax form for implementable function aliases: ```rust -/// An `Iterator` that yields `Result`s. -trait ResultIterator = Iterator> { - type Ok; - type Err; +trait Frob { + fn frob(&self); +} + +trait Alias = Frob { + fn method = Self::frob; // `Alias::method()` is equivalent to `Frob::frob()` } ``` -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). +Effect keywords like `const`, `async`, or `unsafe` do not need to be specified. -This feature could even allow GATification of `Iterator` (or `FnMut`, etc) -without variance bounds: +You are allowed to specify generic parameters, in order to reorder them. But you +don't have to: ```rust -pub trait LendingIterator { - type LentItem<'a> - where - Self: 'a; - - fn next<'a>(&'a mut self) -> Option>; +trait Frob { + fn frob(&self); } -// `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; +trait Alias = Frob { + fn alias = Self::frob; // OK + fn alias2 = Self::frob; // Also OK + //fn alias3 = Self::frob; // ERROR, as would not be implementable } ``` -### Name conflicts +#### Non-implementable `fn`s -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. +A trait alias body can also contain non-alias `fn`s, with bodies. These are not +implementable: ```rust -//! foolib -#![feature(associated_type_defaults)] +trait Frob { + fn frob(&self) -> i32; +} -pub trait Foo { - type Assoc; - /// Added in v1.1 - type WrappedAssoc = Result; +trait Alias = Frob { + #[must_use] + fn frob_twice(&self) -> i32 { + self.frob() + self.frob() + } } ``` +This is similar to defining an extension trait like +[`Itertools`](https://docs.rs/itertools/latest/itertools/trait.Itertools.html). +(One difference from extension traits is that trait aliases do not create their +own `dyn` types.) + +# Drawbacks + +- 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. In particular, trait alias bodies introduce a + large amount of new syntax and complexity, but will likely be rarely used. +- There is a lot of overlap between trait alias bodies and extension traits. + +# Rationale and alternatives + +## Require an attribute to mark the trait as implementable + +We could require an attribute on implementable aliases; e.g. `#[implementable] +trait Foo = ...`. However, there is not much reason to opt out of +implementability. + +## No trait alias bodies + +Not including this part of the proposal would significantly decrease the overall +complexity of the feature. However, it would also reduce its power: trait +aliases could no longer be used to rename trait items, and naming conflicts in +multi-primary-trait aliases would be impossible to resolve. + +## No non-implementable items in trait alias bodies + +Such items don't have much utility from a backward-compatibility perspective, +and overlap with extension traits. However, the cost of allowing them is very +low. + +## Unconstrained generic parameters + +A previous version of this RFC required generic parameters of implementable +trait aliases to be used as generic parameters of a primary trait of the alias. +This restriction was meant to avoid surprising errors: + ```rust -//! aliaslib -extern crate foolib; +trait Foo = Copy; -pub trait Alias = foolib::Foo { - /// Added in v1.1 - type WrappedAssoc = Option; -} +#[derive(Clone)] +struct MyType; + +impl Foo for MyType {} // ERROR: `T`` is unconstrained ``` ```rust -//! thirdlib -extern crate foolib; -extern crate aliaslib; +trait Foo = Iterator; + +struct MyType; -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<()>; +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. + +## Allow `impl Foo + Bar for Type { ... }` directly, without an alias + +It's a forward-compatibility hazard, with no use-case that I can see. + +## Implementing aliases with 0 primary traits + +We could allow implementing aliases with no primary traits, as a no-op. However, +I don't see the point in it. + +# Prior art + +- [`trait_transformer` macro](https://github.com/google/impl_trait_utils) + +# Unresolved questions + +- How does `rustdoc` render these? + +# Future possibilities + +- New kinds of bounds: anything that makes `where` clauses more powerful would + make this feature more powerful as well. + - Variance bounds could allow this feature to support backward-compatible + GATification. +- `trait Foo: Copy = Iterator;` could be allowed as an alternative syntax to + `trait Foo = Iterator where Self: Copy;`. +- We could allow trait aliases to define their own defaults for `impl`s. One + possibility is [the `default partial impl` syntax I suggested on + IRLO](https://internals.rust-lang.org/t/idea-partial-impls/22706/). +- We could allow implementable `fn` aliases in non-alias `trait` definitions. From 4879d5799ad6a0122271db1d209bf2df7412912a Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 10 Apr 2025 19:21:17 -0400 Subject: [PATCH 23/45] Address comments - Add section on `dyn` - Discuss `Deref` example - Various minor fixes --- text/3437-implementable-trait-alias.md | 134 +++++++++++++++++++++---- 1 file changed, 115 insertions(+), 19 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index a283de9e576..e0ce3fab70c 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -178,23 +178,26 @@ With today's `trait_alias`, it wouldn't make much difference for `downstream`. ## Splitting a trait -Consider this humble library: +To exemplify this use-case, we will use [Niko Matsakis’s proposal to split the +`Deref` +trait](https://github.com/rust-lang/rust/pull/135881#issuecomment-2718417230). + +`Deref` currently looks like this: ```rust -trait Frob { - fn frazzle(&self); - fn brop(&self); +pub trait Deref { + type Target: ?Sized; + + fn deref(&self) -> &Self::Target; } ``` -Now, let us imagine that the authors of `frob-lib` realize that some types can -only implement one of `frazzle` or `brop`, but not both. They want to split the -trait in two. But, as with our previous example, there is no way to do this in a -backward-compatible way without painful compromises. +Niko wants to split off the `type Target` part into a separate `Receiver` +supertrait. But there is no backward-compatible way to do this at present. ## Removing a `Sized` bound -Consider this other humble library: +Consider this humble library: ```rust trait Frob { @@ -205,8 +208,8 @@ trait Frob { ``` Currently, `Frob::Frobber` has a `Sized` bound, but the signature of `frob()` -doesn't require it. However, there is no good way at present for `frob-lib` to -remove the bound. +doesn't require it. However, there is no backward-compatible way at present for +`frob-lib` to remove the bound. ## Renaming trait items @@ -353,6 +356,22 @@ impls for the two underlying traits. Or, alternatively, you can give the trait alias a body, and define item aliases with distinct names for each of the conflicting items. +We can use this to split the `Deref` trait, as suggested in the motivation section: + +```rust +//! New `Deref` + +pub trait Reciever { + type Target: ?Sized; +} + +pub trait DerefToTarget: Reciever { + fn deref(&self) -> &Self::Target; +} + +pub trait Deref = Receiver + DerefToTarget; +``` + # Reference-level explanation ## Implementing trait aliases @@ -418,8 +437,8 @@ 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). - fn next(&mut self) -> i32 { - -27 + fn next(&mut self) -> Option { + Some(-27) } } ``` @@ -440,8 +459,8 @@ impl IntIterator for Baz { // so `Item` must be `i32` // and we don't need to specify it. - fn next(&mut self) -> i32 { - -27 + fn next(&mut self) -> Option { + Some(-27) } } ``` @@ -494,7 +513,7 @@ trait IntIter = Iterator where Self: Clone; 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()` +IntIter::clone(&iter); 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(); @@ -763,6 +782,64 @@ This is similar to defining an extension trait like (One difference from extension traits is that trait aliases do not create their own `dyn` types.) +## Interaction with `dyn` + +Trait aliases do not define their own `dyn` types. This RFC does not change that +pre-existing behavior. However, we do make one change to which trait aliases +also define a type alias for a trait object. If a trait alias contains multiple +non-auto traits (primary or not), but one of them is a subtrait of all the +others, then the corresponding `dyn` type for that trait alias is now an alias +for the `dyn` type for that subtrait. + +This is necessary to support the `Deref` example from earlier. + +```rust +trait Foo { + fn foo(&self); +} +trait Bar: Foo { + fn bar(&self); +} + +trait FooBar = Foo + Bar; // `dyn FooBar` is an alias of `dyn Bar` +trait FooBar2 = Foo +where + Self: Bar; // `dyn FooBar2` is also an alias of `dyn Bar` +``` + +N.B.: when using implementable trait aliases to split a trait into two parts +*without* a supertrait/subtrait relationship between them, you have to be +careful in order to preserve `dyn` compatiblilty. + +```rust +trait Foo { + fn foo(&self); +} +trait Bar { + fn bar(&self); +} + +trait FooBar = Foo + Bar; // `dyn FooBar` is not a valid type! +``` + +To make it work, you can do: + +```rust +trait Foo { + fn foo(&self); +} + +trait Bar { + fn bar(&self); +} + +#[doc(hidden)] +trait FooBarDyn: Foo + Bar {} +impl FooBarDyn for T {} + +trait FooBar = Foo + Bar + FooBarDyn; // `dyn FooBar` now works just fine +``` + # Drawbacks - The fact that `trait Foo = Bar + Send;` means something different than `trait @@ -786,6 +863,10 @@ complexity of the feature. However, it would also reduce its power: trait aliases could no longer be used to rename trait items, and naming conflicts in multi-primary-trait aliases would be impossible to resolve. +It's this last issue especially that leads me to not relegate this to a future +possibility. Adding a defaulted item to a trait should at most require minor +changes to dependents, and restructuring a large `impl` block is not “minor”. + ## No non-implementable items in trait alias bodies Such items don't have much utility from a backward-compatibility perspective, @@ -830,7 +911,8 @@ even at the risk of potential confusion. ## Allow `impl Foo + Bar for Type { ... }` directly, without an alias -It's a forward-compatibility hazard, with no use-case that I can see. +It's a forward-compatibility hazard (if the traits gain items with conflicting +names), with no use-case that I can see. ## Implementing aliases with 0 primary traits @@ -851,9 +933,23 @@ I don't see the point in it. make this feature more powerful as well. - Variance bounds could allow this feature to support backward-compatible GATification. -- `trait Foo: Copy = Iterator;` could be allowed as an alternative syntax to - `trait Foo = Iterator where Self: Copy;`. - We could allow trait aliases to define their own defaults for `impl`s. One possibility is [the `default partial impl` syntax I suggested on IRLO](https://internals.rust-lang.org/t/idea-partial-impls/22706/). - We could allow implementable `fn` aliases in non-alias `trait` definitions. +- We could allow any `impl` block to implement items from supertraits of its + primary trait(s). + - This would allow splitting a trait into a supertrait and a subtrait without + having to give the subtrait a new name. + - However, it would make it more difficult to deduce what traits an `impl` + block is implementing. + - In addition, it poses a danger if an `unsafe` subtrait depends on an + `unsafe` marker supertrait: you could implement the subtrait, carefully + checking that you meet its preconditions, while not realizing that you are + also implementing the supertrait and need to check its conditions as well. + - And even if the traits are not `unsafe`, they could still have preconditions + that are important for correctness. Users should never be committing to such + things unknowingly. +- We could add an attribute for trait aliases to opt in to generating their own + `dyn` type. + - This could be prototyped as a proc macro. From 55f34c095b6476f4aca52adb6123edb2a364d56a Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 10 Apr 2025 19:40:33 -0400 Subject: [PATCH 24/45] Fix a bug --- text/3437-implementable-trait-alias.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index e0ce3fab70c..9e0aa7c9326 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -837,7 +837,9 @@ trait Bar { trait FooBarDyn: Foo + Bar {} impl FooBarDyn for T {} -trait FooBar = Foo + Bar + FooBarDyn; // `dyn FooBar` now works just fine +trait FooBar = Foo + Bar +where + Self: FooBarDyn; // `dyn FooBar` now works just fine ``` # Drawbacks From 5e4b65b3c4c89679d922d12e4221398a343d0dde Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 10 Apr 2025 20:59:23 -0400 Subject: [PATCH 25/45] Minor notes --- 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 9e0aa7c9326..b93bd7df9a8 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -856,7 +856,8 @@ where We could require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. However, there is not much reason to opt out of -implementability. +implementability. On the other hand, users may not want to commit to the primary +vs secondary trait distinction immediately. ## No trait alias bodies @@ -919,7 +920,8 @@ names), with no use-case that I can see. ## Implementing aliases with 0 primary traits We could allow implementing aliases with no primary traits, as a no-op. However, -I don't see the point in it. +I don't see the point in it, and there is a risk of people thinking it does +something when it does not. # Prior art From 3933112acb5b9e0fc6ed2ff1b2ad2a0122663b1b Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 10 Apr 2025 22:19:23 -0400 Subject: [PATCH 26/45] Reorder a few paragraphs --- text/3437-implementable-trait-alias.md | 38 ++++++++++++++------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index b93bd7df9a8..5726639b68b 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -622,6 +622,26 @@ trait Alias = Foo { `::AssocVec` means the same thing as `Vec<::Assoc>`, and `::ASSOC_PLUS_1` is equivalent to `const { ::ASSOC + 1 }`. +Items defined in a trait alias body shadow items of the same name in a primary +trait. + +```rust +trait Foo { + type Assoc; +} + +trait Bar { + type Assoc; +} + +trait FooBar = Foo + Bar { + type Assoc = ::Assoc; // `FooBar::Assoc` will resolve to `Foo::Assoc` + type BarAssoc = ::Assoc; +} +``` + +#### Implementability + To be implementable, a `type` or `const` alias item must obey certain restrictions. It must either be set equal to an item of a primary trait of the alias: @@ -687,24 +707,6 @@ trait Bar = Foo>> { } ``` -Items defined in a trait alias body shadow items of the same name in a primary -trait. - -```rust -trait Foo { - type Assoc; -} - -trait Bar { - type Assoc; -} - -trait FooBar = Foo + Bar { - type Assoc = ::Assoc; // `FooBar::Assoc` will resolve to `Foo::Assoc` - type BarAssoc = ::Assoc; -} -``` - #### GATs in type alias bodies Type alias bodies can also contain GATs. These are also subject to the From 0e73504cbab410ba247ff5244345daea17caf6d8 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Fri, 11 Apr 2025 01:12:25 -0400 Subject: [PATCH 27/45] No where clauses on alias items --- text/3437-implementable-trait-alias.md | 36 ++++++++++++++++++-------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 5726639b68b..6e9356dc5b4 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -602,10 +602,10 @@ To resolve these conflicts, you can use trait alias bodies, as described below. ## Bodies for trait aliases -Trait aliases can now optionally contain a body, specifying aliases for various -items. These can be types, constants, or functions. +Trait aliases can now optionally contain a body, which specifies various *alias +items*. These can be types, constants, or functions. -### `type`s and `const` items in trait alias bodies +### `type`s and `const` alias items in trait alias bodies ```rust trait Foo { @@ -622,8 +622,8 @@ trait Alias = Foo { `::AssocVec` means the same thing as `Vec<::Assoc>`, and `::ASSOC_PLUS_1` is equivalent to `const { ::ASSOC + 1 }`. -Items defined in a trait alias body shadow items of the same name in a primary -trait. +Alias items defined in a trait alias body shadow items of the same name in +primary traits. ```rust trait Foo { @@ -640,6 +640,9 @@ trait FooBar = Foo + Bar { } ``` +As aliases, `type` and `const` alias items do not require or accept bounds or +`where` clauses; these are taken from the thing being aliased. + #### Implementability To be implementable, a `type` or `const` alias item must obey certain @@ -709,9 +712,9 @@ trait Bar = Foo>> { #### GATs in type alias bodies -Type alias bodies can also contain GATs. These are also subject to the -implementability rules, though reordering generic parameters does not inhibit -implementability. +Type alias bodies can also contain GAT alias items. These are also subject to +the implementability rules, though reordering generic parameters does not +inhibit implementability. ```rust trait Foo { @@ -727,12 +730,15 @@ trait Alias = Foo { } ``` +### Bounds + ### `fn`s in type alias bodies #### Implementable `fn`s -Trait alias bodies can also contain aliases for methods of its primary trait(s). -This involves a new syntax form for implementable function aliases: +Trait alias bodies can also contain function alias items for methods of its +primary trait(s). This involves a new syntax form for implementable function +aliases: ```rust trait Frob { @@ -744,7 +750,8 @@ trait Alias = Frob { } ``` -Effect keywords like `const`, `async`, or `unsafe` do not need to be specified. +Effect keywords like `const`, `async`, or `unsafe` do not need to be specified, +and are not permitted. You are allowed to specify generic parameters, in order to reorder them. But you don't have to: @@ -761,6 +768,9 @@ trait Alias = Frob { } ``` +Just like `type` alias items, implementable `fn` alias items neither require nor +accept `where` clauses or bounds of any sort. + #### Non-implementable `fn`s A trait alias body can also contain non-alias `fn`s, with bodies. These are not @@ -784,6 +794,10 @@ This is similar to defining an extension trait like (One difference from extension traits is that trait aliases do not create their own `dyn` types.) +These non-alias function items can specify `where` clauses and bounds like any +other function item. They also have the same default `Sized` bounds on their +generic type parameters. + ## Interaction with `dyn` Trait aliases do not define their own `dyn` types. This RFC does not change that From 2cb9aee72d990778ed0aae62c76378c6ec15f350 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Fri, 11 Apr 2025 01:16:53 -0400 Subject: [PATCH 28/45] Implementable function aliases don't need `extern` --- text/3437-implementable-trait-alias.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 6e9356dc5b4..ba07f798413 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -640,7 +640,7 @@ trait FooBar = Foo + Bar { } ``` -As aliases, `type` and `const` alias items do not require or accept bounds or +As aliases, `type` and `const` alias items neither require nor accept bounds or `where` clauses; these are taken from the thing being aliased. #### Implementability @@ -750,8 +750,8 @@ trait Alias = Frob { } ``` -Effect keywords like `const`, `async`, or `unsafe` do not need to be specified, -and are not permitted. +Modifers like `const`, `async`, `unsafe`, or `extern "C"` are neither required +nor accepted. You are allowed to specify generic parameters, in order to reorder them. But you don't have to: From 426324f721eb06e8d38846b11052a9faf886f089 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 4 Jun 2025 19:17:56 -0400 Subject: [PATCH 29/45] 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 ba07f798413..e2d8061ade6 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -217,7 +217,7 @@ Consider this verbose library: ```rust trait TraitForFrobbing { - type TypeThatEnablesFrobbing: + type TypeThatEnablesFrobbing; fn perform_the_frobbing_operation_posthaste(&self, frobber: &Self::TypeThatEnablesFrobbing); } From a9169d130e01e673802cad8b0f78512a29c697b8 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 4 Jun 2025 19:20:40 -0400 Subject: [PATCH 30/45] Fix more typos --- text/3437-implementable-trait-alias.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index e2d8061ade6..732c1e1d044 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -268,7 +268,7 @@ impl Frob for MyType { ## Bodies for trait aliases -Trait aliases can also now sepcify an optional body, which can contain various +Trait aliases can also now specify an optional body, which can contain various items. These items are themselves aliases for items defined on the respective traits. @@ -361,11 +361,11 @@ We can use this to split the `Deref` trait, as suggested in the motivation secti ```rust //! New `Deref` -pub trait Reciever { +pub trait Receiver { type Target: ?Sized; } -pub trait DerefToTarget: Reciever { +pub trait DerefToTarget: Receiver { fn deref(&self) -> &Self::Target; } @@ -573,7 +573,7 @@ trait Bar { trait FooBar = Foo + Bar; ``` -If the confliting items all have defaults, the alias will be implementable, but +If the conflicting items all have defaults, the alias will be implementable, but overriding the defaults will not be possible. ```rust @@ -750,7 +750,7 @@ trait Alias = Frob { } ``` -Modifers like `const`, `async`, `unsafe`, or `extern "C"` are neither required +Modifiers like `const`, `async`, `unsafe`, or `extern "C"` are neither required nor accepted. You are allowed to specify generic parameters, in order to reorder them. But you @@ -825,7 +825,7 @@ where N.B.: when using implementable trait aliases to split a trait into two parts *without* a supertrait/subtrait relationship between them, you have to be -careful in order to preserve `dyn` compatiblilty. +careful in order to preserve `dyn` compatibility. ```rust trait Foo { From 7d101c2536257c230e565406f2822cb67982ec15 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 4 Jun 2025 19:44:49 -0400 Subject: [PATCH 31/45] Const future possibilities --- text/3437-implementable-trait-alias.md | 50 +++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 732c1e1d044..a80c56d7d14 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -949,10 +949,6 @@ something when it does not. # Future possibilities -- New kinds of bounds: anything that makes `where` clauses more powerful would - make this feature more powerful as well. - - Variance bounds could allow this feature to support backward-compatible - GATification. - We could allow trait aliases to define their own defaults for `impl`s. One possibility is [the `default partial impl` syntax I suggested on IRLO](https://internals.rust-lang.org/t/idea-partial-impls/22706/). @@ -973,3 +969,49 @@ something when it does not. - We could add an attribute for trait aliases to opt in to generating their own `dyn` type. - This could be prototyped as a proc macro. + +## New kinds of bounds + +Anything that makes `where` clauses more powerful would make this feature more +powerful as well. + +For example: + +- If we could write bounds for the `const`ness of a method, that could allow +emulating some of [const traits](https://github.com/rust-lang/rfcs/pull/3762)—or +even form part of the desugaring for that feature: + +```rust +pub trait PartialEq +where + Rhs: ?Sized, +{ + fn eq(&self, other: &Rhs) -> bool; + + fn ne(&self, other: &Rhs) -> bool { + !(self.eq(other)) + } +} + +trait ConstPartialEq = PartialEq +where + Self::eq: const, + Self::ne: const; // 🚲🏠 +``` + +- If we could write a bound requiring that a GAT not use is lifetime, that could +enable retrofitting `LendingIterator` into `Iterator`: + +```rust +pub trait LendingIterator { + type Item<'a> + where + Self: 'a; + + fn next(&mut self) -> Option>; +} + +pub trait Iterator = Iterator +where + for<'a> Self::Item<'a>: doesnt_depend_on<'a>; // 🚲🏠 +``` From 5fa4230d5650994ec38ee41355d3733543f71e70 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 5 Jun 2025 12:26:01 -0400 Subject: [PATCH 32/45] `LendingIterator` --- text/3437-implementable-trait-alias.md | 63 ++++++++++++++++---------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index a80c56d7d14..9285a664fcc 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -178,8 +178,9 @@ With today's `trait_alias`, it wouldn't make much difference for `downstream`. ## Splitting a trait -To exemplify this use-case, we will use [Niko Matsakis’s proposal to split the -`Deref` +### `Deref` → `Receiver` + `Deref` + +[Niko Matsakis wants to split up the `Deref` trait](https://github.com/rust-lang/rust/pull/135881#issuecomment-2718417230). `Deref` currently looks like this: @@ -193,7 +194,23 @@ pub trait Deref { ``` Niko wants to split off the `type Target` part into a separate `Receiver` -supertrait. But there is no backward-compatible way to do this at present. +supertrait. But there is no good backward-compatible way to do this at present. + +### `Iterator` → `LendingIterator` + `Iterator` + +Once the necessary language features are stabilized, the library team will +likely want to add a `LendingIterator` trait to the standard library, that looks +like this: + +```rust +pub trait LendingIterator { + type Item<'a> where Self: 'a; + fn next(&mut self) -> Option>; +} +``` + +Ideally, every `Iterator` should automatically be a `LendingIterator`. But, +again, there is no good way to do this right now. ## Removing a `Sized` bound @@ -324,6 +341,20 @@ impl QuiteVerboseAlias for () { } ``` +This could be used to add `LendingIterator` as a supertrait of `Iterator`, as +mentioned in the motivation section: + +```rust +pub trait LendingIterator { + type Item<'a> where Self: 'a; + fn next(&mut self) -> Option>; +} + +pub trait Iterator = for<'a> LendingIterator = ::Item> { + type Item; +} +``` + ## Implementing trait aliases for multiple traits Trait aliases that combine multiple traits with `+` are also implementable: @@ -975,11 +1006,10 @@ something when it does not. Anything that makes `where` clauses more powerful would make this feature more powerful as well. -For example: - -- If we could write bounds for the `const`ness of a method, that could allow -emulating some of [const traits](https://github.com/rust-lang/rfcs/pull/3762)—or -even form part of the desugaring for that feature: +For example, if we could write bounds for the `const`ness of a method, that +could allow emulating some of [const +traits](https://github.com/rust-lang/rfcs/pull/3762)—or even form part of the +desugaring for that feature: ```rust pub trait PartialEq @@ -998,20 +1028,3 @@ where Self::eq: const, Self::ne: const; // 🚲🏠 ``` - -- If we could write a bound requiring that a GAT not use is lifetime, that could -enable retrofitting `LendingIterator` into `Iterator`: - -```rust -pub trait LendingIterator { - type Item<'a> - where - Self: 'a; - - fn next(&mut self) -> Option>; -} - -pub trait Iterator = Iterator -where - for<'a> Self::Item<'a>: doesnt_depend_on<'a>; // 🚲🏠 -``` From 20d8ba0b672359e181dece31188af6047dcd9fc2 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 5 Jun 2025 14:32:52 -0400 Subject: [PATCH 33/45] Target features --- text/3437-implementable-trait-alias.md | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 9285a664fcc..aff43ee4442 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -1028,3 +1028,35 @@ where Self::eq: const, Self::ne: const; // 🚲🏠 ``` + +Additionally, if we could write bounds for the target features required by a +function, that could also be leveraged by implementable aliases: + +```rust +trait FrobWithAvx2 { + #[target_feature(enable = "avx2")] + fn frob(self); +} + +trait FrobWithNoTargetFeatures = FrobWithAvx2 +where + Self::frob: needs_target_features!(""); // 🚲🏠 +``` + +Additional language extensions might be necessary to handle cases where the set +of target features a trait implementation may or may not use is large or open: + +```rust +trait FrobWithUnknownTargetFeatures { + #[target_feature(enable = "*")] // 🚲🏠 + fn frob(self); +} + +trait FrobWithAvx2 = FrobWithUnknownTargetFeatures; +where + Self::frob: needs_target_features!("avx2"); // 🚲🏠 + +trait FrobWithNoTargetFeatures = FrobWithAvx2 +where + Self::frob: needs_target_features!(""); // 🚲🏠 +``` From 5415e73b1ecb3ca35fd2d076274bed7a6953fc19 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 5 Jun 2025 15:03:03 -0400 Subject: [PATCH 34/45] Remove implementability restrictions on `const` and `type` alias items --- text/3437-implementable-trait-alias.md | 49 ++++++++++---------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index aff43ee4442..cd4b1f5b3b9 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -355,6 +355,10 @@ pub trait Iterator = for<'a> LendingIterator = ::Item } ``` +You’ll note that `Iterator`’s body does not explicitly define `Item`. Instead, +it’s defined implicitly by the `for<'a> ::Item<'a> = +::Item` clause in the alias definition. + ## Implementing trait aliases for multiple traits Trait aliases that combine multiple traits with `+` are also implementable: @@ -634,7 +638,9 @@ To resolve these conflicts, you can use trait alias bodies, as described below. ## Bodies for trait aliases Trait aliases can now optionally contain a body, which specifies various *alias -items*. These can be types, constants, or functions. +items*. These can be types, constants, or functions. It must always be possible +to derive the value of these items from the implementations of the aliased +traits; compilation will fail otherwise. ### `type`s and `const` alias items in trait alias bodies @@ -672,13 +678,9 @@ trait FooBar = Foo + Bar { ``` As aliases, `type` and `const` alias items neither require nor accept bounds or -`where` clauses; these are taken from the thing being aliased. - -#### Implementability +`where` clauses; these are taken from the things being aliased. -To be implementable, a `type` or `const` alias item must obey certain -restrictions. It must either be set equal to an item of a primary trait of the -alias: +`type` and `const` alias items may appear in implementations of the alias: ```rust trait Foo { @@ -697,9 +699,8 @@ impl Alias for () { } ``` -Or, the trait alias must set an associated type of the primary trait equal to a -generic type, with the alias item as a generic parameter of that type. For -example, here is +Alias items may be defined *implicitly*, through bounds on the trait alias +itself. For example, here is [`TryFuture`](https://docs.rs/futures-core/latest/futures_core/future/trait.TryFuture.html) as an implementable trait alias: @@ -729,23 +730,9 @@ impl TryFuture for AlwaysFails { } ``` -The generic parameter can also be nested: - -```rust -trait Foo { - type Assoc; -} - -trait Bar = Foo>> { - type Foo; -} -``` - #### GATs in type alias bodies -Type alias bodies can also contain GAT alias items. These are also subject to -the implementability rules, though reordering generic parameters does not -inhibit implementability. +Type alias bodies can also contain GAT alias items: ```rust trait Foo { @@ -755,9 +742,9 @@ trait Foo { } trait Alias = Foo { - type Tag<'a, U, T> = Self::Gat<'a, T, U>; // Implementable - type GatVec<'a, T, U> = Self::Gat<'a, Vec, U>; // Not implementable - type GatSame<'a, T> = Self::Gat<'a, T, T>; // Not implementable + type Tag<'a, U, T> = Self::Gat<'a, T, U>; + type GatVec<'a, T, U> = Self::Gat<'a, Vec, U>; + type GatSame<'a, T> = Self::Gat<'a, T, T>; } ``` @@ -784,8 +771,8 @@ trait Alias = Frob { Modifiers like `const`, `async`, `unsafe`, or `extern "C"` are neither required nor accepted. -You are allowed to specify generic parameters, in order to reorder them. But you -don't have to: +You are allowed to specify the generic parameters, in order to reorder them. But +you don't have to: ```rust trait Frob { @@ -917,7 +904,7 @@ It's this last issue especially that leads me to not relegate this to a future possibility. Adding a defaulted item to a trait should at most require minor changes to dependents, and restructuring a large `impl` block is not “minor”. -## No non-implementable items in trait alias bodies +## No non-implementable `fn`s` in trait alias bodies Such items don't have much utility from a backward-compatibility perspective, and overlap with extension traits. However, the cost of allowing them is very From 87f86e16252bc285f5158f3c3259d7af7ce0be71 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 5 Jun 2025 19:07:15 -0400 Subject: [PATCH 35/45] Fix minor mistake in example --- 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 cd4b1f5b3b9..3a4fe43a6bf 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -1043,7 +1043,7 @@ trait FrobWithAvx2 = FrobWithUnknownTargetFeatures; where Self::frob: needs_target_features!("avx2"); // 🚲🏠 -trait FrobWithNoTargetFeatures = FrobWithAvx2 +trait FrobWithNoTargetFeatures = FrobWithUnknownTargetFeatures where Self::frob: needs_target_features!(""); // 🚲🏠 ``` From f9547f90378746f4c73218a463e6071ce7201133 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sun, 8 Jun 2025 01:08:32 -0400 Subject: [PATCH 36/45] =?UTF-8?q?Explain=20why=20subtrait=20impls=20can?= =?UTF-8?q?=E2=80=99t=20impl=20supertraits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- text/3437-implementable-trait-alias.md | 47 +++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 3a4fe43a6bf..a2ee9826ae2 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -886,14 +886,51 @@ where # Rationale and alternatives -## Require an attribute to mark the trait as implementable +## Allow subtrait implementations to include supertrait items directly + +You’ll note, in the `Deref`/`Receiver` example, that we had to create a new +trait called `DerefToTarget`: + +```rust +//! New `Deref` + +pub trait Receiver { + type Target: ?Sized; +} + +pub trait DerefToTarget: Receiver { + fn deref(&self) -> &Self::Target; +} + +pub trait Deref = Receiver + DerefToTarget; +``` + +If we “just” allowed implementations of any trait to also implement their +supertraits, as has been proposed by others, this extra trait would not be +necessary. + +However, this RFC very deliberately does *not* propose that. The reason for this +is that **a trait `impl` is more than just its items**. This is most apparent with +marker traits: implementing a trait like `Eq` does not require defining any +items at all, but it imposes important restrictions on the implementing type +nevertheless; if those requirements are not upheld, all kinds of bugs could +occur. If the trait is `unsafe`, an erroneous implementation could even be +unsound! + +Because of these risks, when a code reviewer encounters a new trait `impl` +block, they should be able to tell, just from the block itself, what new traits +are being implemented, and therefore what new invariants must be upheld for +those implementations to be valid. If subtrait `impl` blocks could silently also +implement supertraits, that would no longer be possible. + +## Require an attribute on implementable aliases We could require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. However, there is not much reason to opt out of implementability. On the other hand, users may not want to commit to the primary vs secondary trait distinction immediately. -## No trait alias bodies +## Don’t have trait alias bodies Not including this part of the proposal would significantly decrease the overall complexity of the feature. However, it would also reduce its power: trait @@ -904,13 +941,13 @@ It's this last issue especially that leads me to not relegate this to a future possibility. Adding a defaulted item to a trait should at most require minor changes to dependents, and restructuring a large `impl` block is not “minor”. -## No non-implementable `fn`s` in trait alias bodies +## Don’t have non-implementable `fn`s` in trait alias bodies Such items don't have much utility from a backward-compatibility perspective, and overlap with extension traits. However, the cost of allowing them is very low. -## Unconstrained generic parameters +## Constrain generic parameters A previous version of this RFC required generic parameters of implementable trait aliases to be used as generic parameters of a primary trait of the alias. @@ -951,7 +988,7 @@ even at the risk of potential confusion. It's a forward-compatibility hazard (if the traits gain items with conflicting names), with no use-case that I can see. -## Implementing aliases with 0 primary traits +## Allow implementing aliases with 0 primary traits We could allow implementing aliases with no primary traits, as a no-op. However, I don't see the point in it, and there is a risk of people thinking it does From 381d6d251d78baeed309926ef04b00d10ed48a9b Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sun, 8 Jun 2025 01:18:20 -0400 Subject: [PATCH 37/45] Minor changes to rationale & alternatives --- text/3437-implementable-trait-alias.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index a2ee9826ae2..2ed48182116 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -913,7 +913,7 @@ However, this RFC very deliberately does *not* propose that. The reason for this is that **a trait `impl` is more than just its items**. This is most apparent with marker traits: implementing a trait like `Eq` does not require defining any items at all, but it imposes important restrictions on the implementing type -nevertheless; if those requirements are not upheld, all kinds of bugs could +nevertheless. If those requirements are not upheld, all kinds of bugs could occur. If the trait is `unsafe`, an erroneous implementation could even be unsound! @@ -923,12 +923,13 @@ are being implemented, and therefore what new invariants must be upheld for those implementations to be valid. If subtrait `impl` blocks could silently also implement supertraits, that would no longer be possible. -## Require an attribute on implementable aliases +## Require an attribute to make aliases implementable We could require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. However, there is not much reason to opt out of -implementability. On the other hand, users may not want to commit to the primary -vs secondary trait distinction immediately. +implementability. And if the alias definer really wants to make their alias +inimplementable, they can simply no include any primary traits (make all traits +secondary). ## Don’t have trait alias bodies @@ -991,8 +992,9 @@ names), with no use-case that I can see. ## Allow implementing aliases with 0 primary traits We could allow implementing aliases with no primary traits, as a no-op. However, -I don't see the point in it, and there is a risk of people thinking it does -something when it does not. +not allowing this enables users deliberately force an alias to be +non-implementable, which is far more useful (e.g., if they don’t want to commit +to which traits to make primary vs secondary). # Prior art From 12a086a40572d7db6d270fa0b0c05e63cfb98729 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Mon, 9 Jun 2025 19:08:58 -0400 Subject: [PATCH 38/45] `default partial impl` in future possibilities --- text/3437-implementable-trait-alias.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 2ed48182116..b325bd8485d 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -1009,6 +1009,9 @@ to which traits to make primary vs secondary). - We could allow trait aliases to define their own defaults for `impl`s. One possibility is [the `default partial impl` syntax I suggested on IRLO](https://internals.rust-lang.org/t/idea-partial-impls/22706/). +- `default partial impl` would also address the case where one wants to split a + trait in two, but the supertrait has methods with default impls in terms of + the subtrait. - We could allow implementable `fn` aliases in non-alias `trait` definitions. - We could allow any `impl` block to implement items from supertraits of its primary trait(s). From 990667b0ed68119e83caeca24e408f2c5bd22f94 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Mon, 9 Jun 2025 21:38:47 -0400 Subject: [PATCH 39/45] Fix typo Co-authored-by: Travis Cross --- 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 b325bd8485d..ec80912631e 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 most trait aliases Also support fully-qualified method call syntax with such aliases. Additionally, allow trait aliases to have bodies, which can contain `type`s, -`const`s, and.or `fn`s. +`const`s, and/or `fn`s. # Motivation From f6c765eb72195949f6a5bdaced306e8e7fca7a23 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Mon, 9 Jun 2025 22:03:52 -0400 Subject: [PATCH 40/45] Mention GAT `'static` issue --- text/3437-implementable-trait-alias.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index ec80912631e..b96def2b4f9 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -359,6 +359,12 @@ You’ll note that `Iterator`’s body does not explicitly define `Item`. Instea it’s defined implicitly by the `for<'a> ::Item<'a> = ::Item` clause in the alias definition. +(N.B: In today’s Rust, the `for<'a> LendingIterator = …` bound implies +`'static`, which is not desired in this case. This is a longstanding issue with +GATs which this RFC does not aim to address. The real `Iterator` alias may end +up looking slightly different, depending on how that issue is eventually +resolved.) + ## Implementing trait aliases for multiple traits Trait aliases that combine multiple traits with `+` are also implementable: From 82c9db5b1dd1b3d823c2fe73057fdeafcd68d3af Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Mon, 9 Jun 2025 22:24:09 -0400 Subject: [PATCH 41/45] Add clarifications and examples for alias items --- text/3437-implementable-trait-alias.md | 66 +++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index b96def2b4f9..5b2b298a2dc 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -684,7 +684,8 @@ trait FooBar = Foo + Bar { ``` As aliases, `type` and `const` alias items neither require nor accept bounds or -`where` clauses; these are taken from the things being aliased. +`where` clauses; these are taken from the things being aliased. (This only +applies to the definitions of the items, not implementations of them.) `type` and `const` alias items may appear in implementations of the alias: @@ -705,6 +706,56 @@ impl Alias for () { } ``` +Such implementations are permitted only if the compiler is able to verify that +they are consistent with the rest of the impl. Notably, the compiler can’t +invert nontrivial `const` expressions (more complicated than simply setting +equal to some other constant). For example, given the following trait and alias: + +```rust +trait Foo { + const ASSOC: i32; +} + +trait Foo2 = Foo { + const ASSOC_TWICE: i32 = Self::ASSOC * 2; +} +``` + +This doesn’t work: + +```rust +impl Foo2 for () { + const ASSOC_TWICE: i32 = 18; + + // ERROR, missing definition for `ASSOC` + // The compiler will not be able to figure out that `ASSOC` must be 9 +} +``` + +However, this does: + +```rust +impl Foo2 for () { + const ASSOC: i32 = 9; + const ASSOC_TWICE: i32 = 18; + // When given `ASSOC`, the compiler is able verify + // that the concrete value specified for `ASSOC_TWICE` in the impl + // is equivalent to the concrete value it calculates + // with `Self::ASSOC * 2`. +``` + +But this doesn’t: + +```rust +impl Foo2 for Box { + const ASSOC: i32 = T::ASSOC; + const ASSOC_TWICE: i32 = Self::ASSOC * 2; // ERROR + // The compiler is not going to perform any symbolic reasoning, + // so won't be able to figure out that `Self::ASSOC * 2` + // must equal `Self::ASSOC * 2` for all possible `T`. +} +``` + Alias items may be defined *implicitly*, through bounds on the trait alias itself. For example, here is [`TryFuture`](https://docs.rs/futures-core/latest/futures_core/future/trait.TryFuture.html) @@ -782,7 +833,7 @@ you don't have to: ```rust trait Frob { - fn frob(&self); + fn frob(&self); } trait Alias = Frob { @@ -795,6 +846,17 @@ trait Alias = Frob { Just like `type` alias items, implementable `fn` alias items neither require nor accept `where` clauses or bounds of any sort. +In implementations, these methods look no different from any other: + +```rust +impl Alias for () { + #[inline] + fn alias2(&self) { + todo!() + } +} +``` + #### Non-implementable `fn`s A trait alias body can also contain non-alias `fn`s, with bodies. These are not From 59f7333dc760b062b844b3c5b5a1ba9f142edc2f Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Tue, 10 Jun 2025 21:40:04 -0400 Subject: [PATCH 42/45] Minor reword --- text/3437-implementable-trait-alias.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 5b2b298a2dc..4b0c36dcdad 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -986,10 +986,11 @@ occur. If the trait is `unsafe`, an erroneous implementation could even be unsound! Because of these risks, when a code reviewer encounters a new trait `impl` -block, they should be able to tell, just from the block itself, what new traits -are being implemented, and therefore what new invariants must be upheld for -those implementations to be valid. If subtrait `impl` blocks could silently also -implement supertraits, that would no longer be possible. +block, they should be able to tell, from just the block’s header and the +definition of the trait it names, what new traits are being implemented, and +therefore what new invariants must be upheld for those implementations to be +valid. If subtrait `impl` blocks could silently also implement supertraits, that +would no longer be possible. ## Require an attribute to make aliases implementable From 63234a9768ba7aa6b44d5a7aa610312aef67ae76 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Tue, 10 Jun 2025 21:45:09 -0400 Subject: [PATCH 43/45] More minor rephrasings --- text/3437-implementable-trait-alias.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 4b0c36dcdad..25b9d14caf1 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -1013,9 +1013,10 @@ changes to dependents, and restructuring a large `impl` block is not “minor” ## Don’t have non-implementable `fn`s` in trait alias bodies -Such items don't have much utility from a backward-compatibility perspective, -and overlap with extension traits. However, the cost of allowing them is very -low. +Such items don't have much utility for preserving backward compatibility, and +overlap with extension traits. However, the cost of allowing them is low. This +RFC is deliberately written to be as expansive as possible, so I chose to +include them. ## Constrain generic parameters From dc7ea62469c369dd790eb21d52fbcedeb2c90ec5 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Tue, 10 Jun 2025 21:48:23 -0400 Subject: [PATCH 44/45] Remove redundant section --- text/3437-implementable-trait-alias.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 25b9d14caf1..1d768fc7cac 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -1083,19 +1083,6 @@ to which traits to make primary vs secondary). trait in two, but the supertrait has methods with default impls in terms of the subtrait. - We could allow implementable `fn` aliases in non-alias `trait` definitions. -- We could allow any `impl` block to implement items from supertraits of its - primary trait(s). - - This would allow splitting a trait into a supertrait and a subtrait without - having to give the subtrait a new name. - - However, it would make it more difficult to deduce what traits an `impl` - block is implementing. - - In addition, it poses a danger if an `unsafe` subtrait depends on an - `unsafe` marker supertrait: you could implement the subtrait, carefully - checking that you meet its preconditions, while not realizing that you are - also implementing the supertrait and need to check its conditions as well. - - And even if the traits are not `unsafe`, they could still have preconditions - that are important for correctness. Users should never be committing to such - things unknowingly. - We could add an attribute for trait aliases to opt in to generating their own `dyn` type. - This could be prototyped as a proc macro. From 6db4f28294972dbdba28650d89e95c364c3bfc9d Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 11 Jun 2025 13:56:25 -0400 Subject: [PATCH 45/45] Fix minor mistake --- text/3437-implementable-trait-alias.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3437-implementable-trait-alias.md b/text/3437-implementable-trait-alias.md index 1d768fc7cac..161e233b2ba 100644 --- a/text/3437-implementable-trait-alias.md +++ b/text/3437-implementable-trait-alias.md @@ -978,9 +978,9 @@ supertraits, as has been proposed by others, this extra trait would not be necessary. However, this RFC very deliberately does *not* propose that. The reason for this -is that **a trait `impl` is more than just its items**. This is most apparent with -marker traits: implementing a trait like `Eq` does not require defining any -items at all, but it imposes important restrictions on the implementing type +is that **a trait `impl` is more than just its items**. This is most apparent +with marker traits: implementing a trait like `Ord` does not require defining +any items at all, but it imposes important restrictions on the implementing type nevertheless. If those requirements are not upheld, all kinds of bugs could occur. If the trait is `unsafe`, an erroneous implementation could even be unsound!