--- title: "Design meeting 2023-10-25: Implementable trait aliases" date: 2023-10-25 tags: T-lang, design-meeting, minutes discussion: https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Design.20meeting.202023-10-25 url: https://hackmd.io/VGOOy3mnQ6SR85ex0NsiCg --- - 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. 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. 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<frob(..): Send> + Send {} impl<T: ?Sized> Frobber for T where T: LocalFrobber<frob(..): Send> + 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<frob(..): Send> + 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 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 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<frob(..): Send> 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, 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 ## 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)<sup>?</sup> `trait` [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html) [GenericParams](https://doc.rust-lang.org/stable/reference/items/generics.html)<sup>?</sup> `=` [TypeParamBounds](https://doc.rust-lang.org/stable/reference/trait-bounds.html)<sup>?</sup> [WhereClause](https://doc.rust-lang.org/stable/reference/items/generics.html#where-clauses)<sup>?</sup> `;` For example, `trait Foo<T> = PartialEq<T> + 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)<sup>?</sup> `trait` [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html) [GenericParams](https://doc.rust-lang.org/stable/reference/items/generics.html)<sup>?</sup> `=` [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)<sup>?</sup> `;` For example, `trait Foo<T> = PartialEq<T> 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<Item = u32>`). 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<T> = PartialEq<T>; // Not implementable trait Foo<T> = Copy; trait Foo<T> = Copy where T: Send; trait Foo<T> = Iterator<Item = T>; trait Foo<T> = Copy where Self: PartialEq<T>; ``` ## 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. ```rust pub trait CopyIterator = Iterator<Item: Copy> 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 { /* ... */ } ``` Bounds on generic parameters are also enforced at the `impl` site. ```rust trait Underlying<T> {} trait Alias<T: Send> = Underlying<T>; 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. ```rust pub trait IntIterator = Iterator<Item = i32> 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 } } ``` Alias `impl`s 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<frob(..): Send> 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`. ## 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<Item = u32> where Self: Clone; let iter = [1_u32].into_iter(); let _: IntIter::Item = IntIter::next(&mut iter); // works let _: <std::array::IntoIter as IntIter>::Item = <std::array::IntoIter as IntIter>::next(); // works //IntIter::clone(&iter); // ERROR: trait `Iterator` has no method named `clone()` let dyn_iter: &mut dyn Iterator<Item = u32> = &mut iter; //IntIter::next(dyn_iter); // ERROR: `dyn Iterator<Item = u32>` does not implement `Clone` let signed_iter = [1_i32].into_iter(); //IntIter::next(&mut signed_iter); // ERROR: Expected `<Self as Iterator>::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<Item = u32> = [1_u32].into_iter(); trait IntIter = Iterator<Item = u32> where Self: Clone; let _: IntIter<Item = u32> = [1_u32].into_iter(); // `Item = u32` is redundant, but allowed //let _: IntIter<Item = f64> = [1.0_f64].into_iter(); // ERROR: `Item = f64` conflicts with `Item = u32` ``` # 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. - 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). - 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 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: ```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) # 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. - `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. --- # Design meeting minutes Attendance: TC, tmandry, scottmcm, eholk, waffle, Jules Bertholet, pnkfelix, nikomatsakis, jubilee Minutes, driver: TC ## Re: Speculative example: GATification of Iterator wffl: I don't think that even with implementable trait alias you can do this[^1], because most iterator adaptors don't work on lending iterator iirc. tmandry: More explicitly that means that those iterator adaptors wouldn't be implemented in the lending case? That would be a poor user experience but could still be done right? jubilee: to what end tho'? Iterator exists *for* its user experience, essentially. If we wanted a unifying abstraction that was actually good, we could find a way to let people write two blanket impls so that the UnifiedIterator could actually define a unified interface in a useful way, but if I recall correctly we can't because we can't do ```rust impl<T> UnifiedIterator for T where T: Iterator { type Item = ...; fn next(&mut self) -> ???; } impl<T> UnifiedIterator for T where T: LendingIterator { type Item = ...; fn next(&mut self) -> ???; } ``` even if we know Iterator and LendingIterator are never implemented for a given type. [^1]: This was also Jubilee's immediate reaction. "Isn't this a lifetime relationship that most Iterator stuff would 'violate' in some way?" TODO(wffl): write an example nikomatsakis (not stated in meeting): I generally agree with what I think jubilee is saying, which is that lending iterators and iterators are fundamentally different in terms of the ownership relations, and while we should absolutely say that every iteratr can be used where a lending iterator is expected, they are not generally unifiable in the same way that `Fn` and `FnMut` and `FnOnce` are not unifiable (i.e., when I take a `impl LendingIterator`, I am agreeing to use that value in a more limited range of ways -- the values I get will not beyond a single `next` call -- in exchange for giving more power to the implementor to reuse data and storage). jubilee: right, it's closer to the reverse, of "LendingIterator is strong, Iterator is weak, because the constraints on the LendingIterator's use is actually stronger, much like the requirement for a Send bound is actually a stronger constraint". ## Trait transformers TC: The document mentions overlap with trait transformers. NM, could you discuss whether and how you view any overlap here? NM: It did surprise me to see that in there. I suppose there is some overlap. One of the use-cases is adding Send bounds. That particular one could be handled by trait aliases. But others could not. Such as traits with methods that are maybe async. NM: This makes sense as a more limited extension. ## Syntactic restrictions in reference explanation/implementability rules wffl: it's a bit weird that the first restriction in reference explanation is syntactic, rather than semantic. this probably does not matter, but it's weird ## Why a syntactic restriction on implementability scottmcm: I find the phrasing in the reference section of the rules for what's implementable using a grammar as strange. `= Foo + Bar;` vs `= Foo where Self : Bar;` being this different doesn't seem like it ought to be meaningful. (Especially if it leads to people needing to write trait aliases as `=where Self: Foo, Self : Bar` to not be implementable, or something.) (copied up higher to go with waffle's same point.) ## Implementability rules should mention the reason why you need to use all generic parameters as generic parameters of the trait wffl: what the title said, it's not *immediately* clear why you need to use generic parameters specifically as generic parameters of the trait. For example I could imagine `impl Foo<T> for Type<T> {}` being valid if `Foo<T>` doesn't use `T` in the trait. wffl: similarly this looks fine: ```rust trait Foo<T> = Iterator<Item = T>; impl Foo<u32> for MyIter { // `type Item` ommited because "uniquely constrained" fn next(&mut self) -> u32 { todo!() } } ``` Jules Bertholet: The idea is to avoid surprising overlap errors. For example, in the example just above, adding a `Foo<i32>` impl would give an overlap error, even though syntactically there seems to be no conflict. Jules Bertholet: Also, if `Foo<T>` doesn't use `T`, how should `impl<T> Foo<T> for ()` be handled? I wanted to avoid all those questions by just saying "no". NM: It may be that none of these rules are adequate. We're saying this desugars to a series of impls for the underlying traits. E.g.: ```rust trait Foo<T> = Bar<Item = Vec<T>> + Baz<T>; ``` NM: So, rather than imposing this restriction at the trait site, we could impose it at the impl site? waffle: Yes. NM: This is a good question, but maybe a minor point in some sense. We can accept more things by making the check at the impl site. But there is some fundamental limit to how accepting we can be. waffle: In some sense it's more consistent to do it at the impl site. If you're returning something like `impl Foo<u32>`... TC: Would it be backward compable to loosen this? wffl: I believe so. tmandry: We could add this as a future possibility or open question. ## Multiple traits TC: I know people want to discuss extending this to multiple traits or otherwise relaxing the rules on whether a trait alias is implementable. What do people think about this? NM: I want us to support auto traits. pnkfelix: Not good enough to support it in Where Clauses? NM: Not sure what we gain by that? pnkfelix: Cut and paste semantics. NM: But it's not cut-and-paste semantics. E.g.: ```rust trait MyIter<T> = MyTrait<T, Item = T>; // It's intended to allow this: impl MyIter<u32> for MyType { } // but this isn't allowed today impl MyTrait<u32, Item = u32> for MyType { } ``` Jules: That's correct, that's one exception to the cut-and-paste. NM: If we look at the motivating example of Send bound aliases. The definition is pretty complex. Adding a bound to return type... that's going to be checked at the impl site I assume. It doesn't seem like cut-and-paste semantics. ```rust pub trait Frobber = LocalFrobber<frob(..): Send> where Self: Send; // <-- where...does this go? impl Frobber for MyType { async fn frob(...) { ... } } ``` waffle: Allowing auto traits is more confusing than less confusing. I'd expect that if you have: ```rust trait A = B + C; impl A for T {} // ...is the same as... impl B for T {} impl C for T {} ``` waffle: If we allow auto traits, then we're implementing the trait but not requiring it. NM: You're effectively saying that those WCs that I'd expect to be enforced at the impl site are not part of the impl, they're not implied by the impl existing. They may be part of WF checks. tmandry: Currently they'd be where clauses on the impl. So you could even implement this alias for a type that never implements `Send`... NM: So you'd get an error when you try to use the impl. Today you might even get an error on the impl itself, but we intend to relax that. scottmcm: The big reason for allowing multiple here is that it would be really valuable to allow someone to split a trait in two. And you can't do that if you can't implement both of those parts through the same impl. That's a reasonable wish for people to have of this feature. waffle: I think that it's reasonable to support implementing multiple traits, but only if all traits joined by `+` are implemented. So this would support splitting a trait, but not having `+Send`. scottmcm: I'd agree with that. NM: This is annoying possible truth. I don't want people to have to write `where Self: Send`. NM: The set of supertraits are the set of both things. NM: I also feel that auto traits are maybe just different. scottmcm: `#[marker]` traits are again something that you have to implement, so You'd just implement it. NM: It's kind of an assertion. People write dummy functions for this, just to check Send. pnkfelix: NM made the correct point it's not a cut-and-paste semantic. Reading the reference, the way this is supposed to work is... ```rust trait MyIter<T> = MyTrait<T, Item = T>; // It's intended to allow this: impl MyIter<u32> for MyType { } // but this isn't allowed today impl MyTrait<u32, Item = u32> for MyType { } ``` It's as if you had... ```rust impl MyTrait<u32> for MyType { type Item = u32; } ``` Any occurrence of `MyTrait<u32>` you now get to infer that item. The combination of the trait definition and the impl block gets you that. Strange but true. NM: It is strange, in some sense. scottmcm: It makes me think we should allow writing it that way. NM: ```rust // what scottmcm meant (according to Niko) impl Iterator<Item = u32> for MyIntIterator { } // what pnkfelix meant where T: Iterator<u32> // "doesn't normally let you know the definition of an assoc type" ``` jubilee: it has some confusing consequences for the fact that different traits use associated types like "type Output" for _wildly_ different reasons. NM: I like this RFC more if we're more ambitious, agreeing with scottmcm. pnkfelix: There are interesting form of reasoning this gives you access to. NM: That's really a result of trait aliases rather than this RFC. tmandry: pnkfelix, are you reacting to this in impl position or in WC position? pnkfelix: In a WC position. pnkfelix: ...I withdraw any negative feedback associated with my surprise. waffle: Agree it would be cool to support just writing the thing down in normal traits. We should reach out to T-types to make sure they don't hate us for this. NM: We should talk with this RFC in general with T-types. NM: But to the degree that this is a desugaring, it doesn't really land in T-types territory. But there may be some caveats. NM: We should strive to it desugaring to a bunch of impls that you could have rewritten in another way. And to the degree we can't do that, maybe we should make our `impl` syntax more flexible. scottmcm: This is making me think of things where the alias wants its own associated type, like imagine: ```rust trait Foo = GenericIterator<Item = Result<_, _>>; // Is this legal? trait alias InfiniteIterator: GenericIterator<GenericItem = Result<Self::Item, !>> { type Item; } ``` scottmcm: Can I write one like above where I didn't fully qualify that? NM: [pnkfelix, example where I think this confusion arises today](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=16088a3106d25db75432fc8fb5b7f3ee) ```rust fn test<X: FooWithItem<u32>>(x: u32) -> X::Item { x // this requires knowing that `X::Item` is `u32` } trait Foo { type Item; } trait FooWithItem<T>: Foo<Item = T> { } impl<T, A> FooWithItem<T> for A where A: Foo<Item = T> { } ``` NM: For better or worse, that's possible today. NM: One of the things I want is that in subtraits, adding bounds to associated types. Maybe that works here? scottmcm: hmm, on nightly `where T: Iterator<Output: Ord>` is valid syntax, right? I guess in a trait alias that one desugars to a `where` in the way that `<Output = Foo>` doesn't? NM: ... ## What's the next step? TC: Even ahead of this meeting, people were excited about this one. Is there anything more we need before proposing FCP merge? pnkfelix: Can we move forward with what this is saying and later do something more ambitious? NM: I don't think so? scottmcm: It gets back to what we think the purpose of an RFC is. Do I want an experiment on this tomorrow? Yes. But is this the RFC I want, not sure. waffle: It'd be good to discuss the future possibilities we discussed in the RFC. tmandry: We do change details of RFCs in the stabilizaton process. Jules: I'll add more things to the future possibilities, especially around the rules on trait parameters. Jules: The discussion has raised questions for me around where clauses and whether what has been proposed is the best way. I'm whether whether the copy-and-paste model of the impl doesn't really apply. scottmcm: My meta-take-away from this meeting is worry about trait aliasse trying to do two different things. That might be some of the confusion around WCs. (The main body of the meeting ended here.) TC: Perhaps, as NM proposed, we could lean more heavily on the desugarings, and expand the RFC to include those desugarings, and then separate out any things that can't be expressed as a desugaring. waffle: Maybe we could write a separate RFC for: ```rust impl Iterator<Item = u32> for MyIter { fn next(&mut self) -> Option<u32> { todo!() } } // this example can be desugared into impl Iterator for MyIter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { todo!() } } ``` waffle: If this could be accepted separately, this RFC gets simpler. Jules: ```rust trait Sub: Iterator<Item = u32>; trait Alias = Iterator where Self: Sub; impl Alias for SomeType { // Don't need `type Item = u32;` fn next(&mut self) -> Option<Self::Item> { todo!() } } ``` waffle: ```rust impl<T> Iterator for MyIter<T> where Self: Iterator<Item = T> { fn next(&mut self) -> Option<T> { todo!() } } // Can theoretically be turned into, but it's not trivial impl<T> Iterator for MyIter<T> { type Item = T; fn next(&mut self) -> Option<T> { todo!() } } ``` wffl: A problem with leading iter and normal one: ```rust // Right now trait Iterator { fn array_chunks<const N: usize>(self) -> ArrayChunks<Self, N> where Self: Sized, { ArrayChunks::new(self) } } ``` ...and `ArrayChunks` does not make sense for a lending iterator, because you can't have two items from a lending iterator at the same time. ...so you'd have to write something like... ```rust // Right now trait LendingIterator { fn array_chunks<const N: usize>(self) -> ArrayChunks<Self, N> where Self: Sized + Iterator, // NEW { ArrayChunks::new(self) } } ``` ...and this breaks impls of `Iterator` (which can technically overwrite `array_chunks`): ```rust impl Iterator for MyType { // ... fn array_chunks<const N: usize>(self) -> ArrayChunks<Self, N> where Self: Sized {todo!()} } ``` ...but maybe that's not true because the `Self: Iterator` bound would be trivial and you don't have to write it in the implementation?... (The meeting ended here.) ## Sem ver pnkfelix: Proposal as written makes implementability a sem-ver guarantee, right? (I.e., someone who writes a trait alias may not realize that what they have written is implicitly implementable, and then it becomes a sem-ver breaking change to add a `+ Copy` or similar ... but then again, is any such change **already** sem-ver breaking? Not sure...) pnkfelix: I was thinking about this as an argument in favor of the `#[implementable]` attribute attached to such aliases, but its a weak argument at best. TC: It's another good reason to consider how loose we could plausibly make the rules for implementability. ## Forward compatibility of blanket impls tmandry: The example using blanket impls with RTN is how someone might approach this once we have RTN. Is it possible to a library to migrate from this... ```rust! pub trait LocalFrobber { async fn frob(&self); } // or whatever RTN syntax is decided on pub trait Frobber: LocalFrobber<frob(..): Send> + Send {} impl<T: ?Sized> Frobber for T where T: LocalFrobber<frob(..): Send> + Send {} ``` ...to a trait alias, without breaking backwards compatibility? ```rust! pub trait LocalFrobber { async fn frob(&self); } pub trait Frobber = LocalFrobber<frob(..): Send> + Send; ``` For that matter, we don't actually have RTN today, so someone might write this instead... ```rust! pub trait LocalFrobber { async fn frob(&self); } pub trait Frobber { fn frob(&self) -> impl Future<Output = ()> + Send; } impl<T: Frobber> LocalFrobber for T { async fn frob(&self) { <Self as Frobber>::frob().await } ``` ...which I *think* could be migrated to the blanket impl with RTN ([see here for rationale](https://rust-lang.zulipchat.com/#narrow/stream/315482-t-compiler.2Fetc.2Fopaque-types/topic/Migration.20path.20to.20trait.20aliases.20and.20RTN/near/392588237)), and hopefully also to trait aliases. ## Simplified trait aliases nikomatsakis: In essence, I think a trait alias... ```rust trait Alias<..> = Bounds0 + ... + BoundsN where WC; ``` ...should be shorthand for this... ```rust trait Alias<..>: Bounds0 + ... + BoundsN where WC {} impl<T, ...> Alias<..> for T where T: Bounds0 + ... + BoundsN, WC {} ``` With, I think, no real differences at all between them *except perhaps* this one, where you can implement an `Alias` directly... ```rust impl Alias<..> for u32 { } ``` ...though that would be effectively "expanded" to impls of the bounds (eventually I'd like you to be able to implement a trait and its supertraits in one impl, but that's separable). What's missing here. Maybe there is no question and this is just me thinking out loud. I guess for thing the `where Self: Send` rule doesn't seem needed. ## Can I directly put types in the impl? scottmcm: It talks about allowing `impl Foo<T = i32> for Bar` via a trait alias. Can it be done directly?