owned this note
owned this note
Published
Linked with GitHub
---
title: "Design meeting 2025-06-11: Evolving trait hierarchies"
tags: ["T-lang", "design-meeting", "minutes"]
date: 2025-06-11
discussion: https://rust-lang.zulipchat.com/#narrow/channel/410673-t-lang.2Fmeetings/topic/Design.20meeting.202025-05-11/
url: https://hackmd.io/6JId0y8LTyCzVMfZFimPqg
---
# Evolving trait hierarchies
## The problem
Libraries frequently discover after having written a trait that some subset of that trait is independently useful, and could be broken out into some smaller supertrait. However, this change cannot be made backwards-compatibly in today's Rust.
Two significant motivating cases are discussed in [RFC 3437 "Implementable trait alias"](https://github.com/Jules-Bertholet/rfcs/blob/implementable-trait-alias/text/3437-implementable-trait-alias.md#deref--receiver--deref) under the heading "splitting a trait".
### Conceptual supertrait: `Deref: Receiver`
The `Deref` trait currently looks like this:
```rust=
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
```
More recently, the `arbitrary_self_types` feature has motivated a more general `Receiver` trait:
```rust=
pub trait Receiver {
type Target: ?Sized;
}
```
Ideally, `Reciever` would be a supertrait of `Deref`:
```rust=
pub trait Receiver {
type Target: ?Sized;
}
pub trait Deref: Receiver {
fn deref(&self) -> &Self::Target;
}
```
but this cannot be done today without a breaking change.
More details are in this Pre-RFC: [Supertrait associated items in subtrait impl](https://hackmd.io/@rust-for-linux-/SkucBLsWxl)
### Conceptual supertrait: `Iterator: LendingIterator`
Similarly, every type that implements today's `Iterator` trait could also (conceptually) be a `LendingIterator`:
```rust=
pub trait LendingIterator {
type Item<'a> where Self: 'a;
fn next(&mut self) -> Option<Self::Item<'_>>;
}
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
```
#### Missing or misnamed parent trait items
Note that, unlike `Deref` and `Receiver`, the names and signatures of the associated items in `LendingIterator` do not match those in `Iterator`. For existing `Iterator` types to implement `LendingIterator`, some bridge code between the two implementations must exist.
### Conceptual supertrait: relaxed bounds
A common practice in the `async` world is to use [the `trait_variant` crate](https://docs.rs/trait-variant/latest/trait_variant/) to make two versions of a trait, one with `Send` bounds on the futures, and one without:
```rust=
#[trait_variant::make(IntFactory: Send)]
trait LocalIntFactory {
async fn make(&self) -> i32;
fn stream(&self) -> impl Iterator<Item = i32>;
fn call(&self) -> u32;
}
// `trait_variant` will generate a conceptual subtrait:
trait IntFactory: Send {
fn make(&self) -> impl Future<Output = i32> + Send;
fn stream(&self) -> impl Iterator<Item = i32> + Send;
fn call(&self) -> u32;
}
```
In this example, a type that implements `IntFactory` also satisfies the requirements for `LocalIntFactory`, but additionally guarantees that the returned types are `Send`.
## Today's solutions
### Add a supertrait
#### Pros
* Matches the conceptual pattern between the traits: one clearly implies the other.
* The "standard" Rust way of doing things.
#### Cons
* Breaking change.
* Requires implementors to split out the implementation into separate `impl` blocks for each trait.
### Add a blanket impl
Rather than declaring `trait A: B`, one can create sibling traits with a blanket impl:
```rust=
trait Supertrait {
// supertrait items
}
trait Subtrait {
// supertrait items + subtrait items
}
impl<T: Subtrait> Supertrait for T {
// impl supertrait items using subtrait items
}
```
#### Pros
* Backwards compatible to introduce `Supertrait`
#### Cons
* Middleware impls are impossible! We'd like to write:
```rust=
struct Middeware<T>(T);
impl<T: Supertrait> Supertrait for Middleware<T> { ... }
impl<T: Subtrait> Subtrait for Middleware<T> { ... }
```
but this overlaps with the blanket impl, and is rejected by the Rust compiler! This is a critical issue for `async` bridge APIs such as tower's `Service` trait, which wants to provide wrappers which implement the `trait_variant`-style `Send`-able when the underlying type implements the `Send` version (see [these notes from a previous design meeting](https://hackmd.io/rmN25qziSHKT4kv-ZC8QPw)).
Other nits:
* The directionality of the impl is less clear.
* Every shared item has two names: `<T as Supertrait>::Item` and `<T as Subtrait>::Item`. Relatedly, the bridge impl must exist even if items are identical.
### Provide no bridging
Another alternative is to provide two totally separate traits with no bridging, requiring users to manually implement both versions of the trait:
```rust=
trait Supertrait { ... }
trait Subtrait { ... }
struct Impl { ... }
impl Supertrait for T { ... }
impl Subtrait for T { ... }
```
#### Pros
* Backwards compatible
* Middleware can be written to bridge either impl
#### Cons
* Requires duplication
* Requires users to restate bounds
* Existing code which implements `Subtrait` cannot be used as `Supertrait`, so APIs which require `Subtrait` cannot be relaxed to `Supertrait`
## Future solutions
### Simple implementable trait aliases
```rust=
trait Foo: Send {
fn foo() -> impl Future<...> + Send;
}
impl Foo for MyType { ... }
// can transition to:
trait LocalFoo {
fn foo() -> impl Future<...>;
}
trait Foo = LocalFoo<foo(...): Send>
where Self: Send;
impl Foo for MyType { ... }
```
#### Pros
* Backwards compatible
* No duplication
#### Cons
* Implementable trait aliases require syntax to distinguish between bounds that are *required* by the impl vs. bounds that are *provided* by the impl. For example, note that `impl Foo for MyType` should not also `impl Send for MyType` despite `Self: Send` being in the bounds. [RFC 3437](https://github.com/rust-lang/rfcs/pull/3437) resolves this by saying that "primary traits" (after `:` but before `where`) are implemented, while bounds following the `where` are required, not implemented.
* Only works where the traits have the exact same set of items. In other words, the subtrait can only be the supertrait plus some additional bounds. This works for the `Send` issue, but not for `Iterator: LendingIterator` or `Deref: Receiver`.
### Trait aliases with items
See [RFC 3437](https://github.com/rust-lang/rfcs/pull/3437) for one proposal.
#### Pros
* This allows subtraits to have additional items that aren't present on the supertraits.
* Bonus: allows for easier introduction of "extension traits" which historically were written with blanket impls.
#### Cons
* It's surprising that an alias would be able to contain items. Such an alias is no longer a simple renaming, but effectively a new set of items that a user might expect to be able to override.
* The impl of `LendingIterator: Iterator` will require additional tools due to overlapping names and cyclical bounds:
```rust=
pub trait LendingIterator {
type Item<'a> where Self: 'a;
fn next(&mut self) -> Option<Self::Item<'_>>;
}
pub trait Iterator = for<'a> LendingIterator<Item<'a> = <Self as Iterator>::Item> {
type Item;
}
```
Specifically, the `for<'a> LendingIterator<Item<'a> = <Self as Iterator>::Item>` specification requires (cyclical) associated type equality, and `T::Item` will now be ambiguous. [RFC 3437](https://github.com/rust-lang/rfcs/pull/3437) proposes to handle this by preferring the trait alias item to the supertrait item.
### Default impls and specialization
See [RFC 1210](https://rust-lang.github.io/rfcs/1210-impl-specialization.html#the-default-keyword) for the specifics of one proposal.
Ths approach begins with the blanket impls solution above but removes the restriction preventing manual implementations:
```rust
trait Supertrait {
fn supertrait_fn();
}
trait Subtrait {
fn supertrait_fn();
fn subtrait_fn();
}
impl<T: Subtrait> Supertrait for T {
default fn supertrait_fn() {
<T as Subtrait>::supertrait_fn();
}
}
struct Middeware<T>(T);
impl<T: Supertrait> Supertrait for Middleware<T> { ... }
// This impl is now allowed because it is "more specific" than the
// blanket impl above.
impl<T: Subtrait> Subtrait for Middleware<T> { ... }
```
#### Pros
* Allows for lots of flexibility: bridging impls can have additional conditional bounds.
#### Cons
* Specialization has been a long-standing source of bugs in the trait solver, and there is no clear path towards fixing the design in preparation for stabilization.
### Implement supertrait via subtrait impl
See [Pre-RFC Supertrait associated items in subtrait impls](https://hackmd.io/@rust-for-linux-/SkucBLsWxl) for one proposal. This approach would allow impls for a subtrait to include the items from a supertrait, thereby implementing the supertrait itself. This would allow splitting a trait into two without breaking existing impls:
```rust=
// Library code:
trait Subtrait {
fn supertrait_item();
fn subtrait_item();
}
// User code:
impl Subtrait for MyType {
fn supertrait_item() { ... }
fn subtrait_item() { ... }
}
// -- can become --
// Library code:
trait Supertrait {
fn supertrait_item();
}
trait Subtrait: Supertrait {
fn subtrait_item();
}
// User code is unchanged from above, no separate `Supertrait`
// impl required
impl Subtrait for MyType {
fn supertrait_item() { ... }
fn subtrait_item() { ... }
}
```
#### Pros
* Resulting trait heirarchy is intuitive: the conceptual supertrait *is* a supertrait.
#### Cons
* Application of `auto` trait definition would have to be conditional on whether an explicit impl of the supertrait exists. This creates a potential ambiguity in the context of blanket impls that provide definitions of the supertrait. This ambiguity also exists if two separate traits have the same parent `auto` trait and no clarifying items are present.
```rust=
trait Supertrait {
fn fn_with_default_impl() {}
}
trait SubtraitOne: auto Supertrait {}
trait SubtraitTwo: auto Supertrait {}
struct MyType;
impl SubtraitOne for MyType {}
impl SubtraitTwo for MyType {}
```
In this case, I (cramertj@) *believe* that the ambiguity can always be resolved by either a definition of an item in the parent trait *or* the implementation of the parent trait must be equivalent.
* Unlike trait aliases, users must explicitly declare whether they implement the subtrait *or* users must always `impl` the supertrait and let the subtrait impl be covered by a blanket impl. For the `trait_variant` / Tower service case, this could look like:
```rust=
trait LocalService { fn call() -> impl Future<...>; }
trait Service: auto LocalService
where
Self: Send + LocalService<call(..): Send> {}
// user chooses whether to implement `LocalService` or `Service`
impl Service for MySendableService { ... }
// middleware impls need to impl both traits:
impl<T: LocalService> LocalService for Middleware<T> { fn call() -> ... { ... } }
impl<T: Service> Service for Middleware<T> {}
// APIs accept `Service` if they need to spawn to threads, `LocalService` if not
```
`LocalService` impls which happen to be appropriately `Send` would not automatically implement `Service`.
* `impl` blocks for traits can now include items that are not declared in the body of the trait definition. This may cause confusion for users unfamiliar with the feature.
* Users will likely want a way to disambiguate item name conflicts. This could be resolved using qualified paths (e.g. `fn PartialEq::partial_eq(&self, ...) { ... }`)
* Similarly to trait aliases, we need a way to distinguish which supertraits can be implemented by subtrait impls and which are bounds. For example:
```rust=
trait MyTrait: Send {}
impl MyTrait for Foo { ... }
```
should cause an error that `Send` is not implemented rather than attempt to implement the `Send` trait. This is primarily an issue for supertraits without items.
Two possible options:
1. Opt-out by moving bounds after `where`: follow the trait alias suggestion of treating bounds following `where` as not-implemented. The above example would then be written `trait MyTrait where Self: Send` in order to opt out of the bound. This could become the default in new editions, but existing editions would need to disable this feature, as existing `trait Foo: Send` declarations would be insta-implementable rather than being treated as bounds.
2. Opt-in using a keyword: prefix supertraits with some keyword, e.g. `trait Subtrait: auto Supertrait { ... }` to indicate that they are implementable.
#### Further possibility: subtrait provides defaults for supertrait items
Example: implement `PartialEq`, `Eq`, `PartialOrd`, and `Ord` by implementing `Ord`
```rust=
trait PartialOrd: auto PartialEq {
fn PartialEq::partial_eq(...) -> ... { ... }
fn partial_cmp(...) -> ...;
}
trait Ord: auto PartialOrd {
fn PartialOrd::partial_cmp(...) -> ... { Some(self.cmp(...)) }
fn cmp(...) -> ...;
}
```
Example: implement `Clone` by implementing `Copy`:
```rust=
trait Copy: auto Clone {
fn Clone::clone(...) -> ... { ... }
}
```
##### Pros
* Allows for lots of code reuse / deduplication.
##### Cons
* This makes the ambiguous/conflicting `auto` supertrait impl problem worse:
```rust=
trait Supertrait {
fn supertrait_item();
}
trait SubtraitOne: auto Supertrait {
fn Supertrait::supertrait_item() {} // provides a default
}
trait SubtraitTwo: auto Supertrait { // provides a *different* default
fn Supertrait::supertrait_item() {
println!("boo!")
}
}
struct MyType;
// ERROR: ambiguous default impl
impl SubtraitOne for MyType {}
impl SubtraitTwo for Mytype {}
```
* Compared with default partial specialization, default item implementations can only be written when they apply to *all* impls of the subtrait. That is, it would not be possible to provide default items only in the case that the subtrait impl matches some extra bound, such as:
```rust=
trait Subtrait { ... }
default impl<T> Supertrait for T where T: Subtrait + Debug { ... }
```
* The compiler must distinguish between a defaulted supertrait item and a new item declaration. The above examples use trait-qualified paths in order to distinguish between the two. This has the additional advantage of guiding the reader to the original definition of the item.
## Recommendation
Author's (cramertj@'s) thoughts: Personally, I prefer the "implement supertrait via subtrait impl" approach, as it provides the closest experience to existing inheritance-based languages and most closely matches the reader's intuition: supertraits *are actually* supertraits, rather than unrelated traits with complex bridging.
Implementable trait aliases may also be independently useful, but adding the ability for trait aliases to have items breaks my personal mental model and feels like it introduces yet another way to model subtrait-like-things (in addition to subtraits and blanket impl'd traits). The addition of separate-but-similar ways to solve problems complicates the experience of new users and creates additional choices for advanced users to consider, complicating the decision-making process and design space (see ["Paradox of Choice"](https://en.wikipedia.org/wiki/The_Paradox_of_Choice)).
---
# Discussion
## Attendance
- People: Xiang Fei, TC, Taylor, scottmcm, Tyler Mandry, JoshT, nikomatsakis, ~Mara~, Jules Bertholet, Eric Holk
## Meeting roles
- Driver: TC
- Minutes: Tomas Sedovic
## Vibe checks
### nikomatsakis
nikomatsakis: The doc ends with
> Author's (cramertj@'s) thoughts: Personally, I prefer the "implement supertrait via subtrait impl" approach, as it provides the closest experience to existing inheritance-based languages and most closely matches the reader's intuition: supertraits are actually supertraits, rather than unrelated traits with complex bridging.
I am generally aligned on that. One difference is that I feel like *trait alias* like `trait Foo = Bar` ought to be equivalent to `trait Foo: Bar` and a blanket impl. Which then, if you have implementing supertrait via subtrait impl, suggests you can implement `Foo` as well in just the same way. Certainly that's how I write trait aliases today, and it allows the story of "if you need to start adding items, you desugar by one level".
There is the tricky widget of how to think about `Send`. But I'm not sure how different that is... what would happen if you did this?
```rust
trait LocalService {
fn method1();
}
trait Service: LocalService + Send {
}
impl Service for T {
fn method1() { }
} // I guess the answer is that you'd get an error, which does suggest, perhaps trait aliases should be limited to "primary + auto", like dyn, for the time being.
```
All that said, I'd be happy to not decide anything about trait aliases and to say "the way to do trait aliases is with child subtraits and blanket impls" continue to be true for the time being.
cramertj: I'd disambiguate this by changing the line above to:
```rust
trait Service: auto LocalService + Send { ... }
```
nikomatsakis: On the point of `auto` syntax in that position I'm...not sure. I have to think on it. It makes sense on its own. And lord knows I've proposed enough abuses of `use` that are unrelated. But there's something about using `auto` in two distinct ways in such close proximity that seems very confusing. Like, you put `auto` on the trait... but not on the auto trait? Perhaps we can find another keyword. On the general *concept* of having a keyword -- I'm +0. I can see that it'd be useful to opt-in.
nikomatsakis: Wait, for Local Service, I believe this approach doesn't solve the problem? In particular, you need a blanket impl, and then you will also have an impl for your type, and that will conflict? We could circumvent that.
cramertj: I imagined you would implement both Service and LocalService for middleware.
nikomatsakis: Ah, I see, ok. I have to think on that. I imagined you writing just a `LocalService` impl for middleware.
cramertj: The example I gave above was:
```rust=
trait LocalService { fn call() -> impl Future<...>; }
trait Service: auto LocalService
where
Self: Send + LocalService<call(..): Send> {}
// user chooses whether to implement `LocalService` or `Service`
impl Service for MySendableService { ... }
// middleware impls need to impl both traits:
impl<T: LocalService> LocalService for Middleware<T> { fn call() -> ... { ... } }
impl<T: Service> Service for Middleware<T> {}
// APIs accept `Service` if they need to spawn to threads, `LocalService` if not
```
### tmandry
I really want to solve this problem (or set of related subproblems), and I appreciate the breakdown of the problem and solution space in this doc.
I like the idea of starting with the ability to implement supertrait items in a subtrait impl.
It may not be enough to deliver the user story we want in all cases – I think there are cases where we probably want a true trait alias that is automatically defined if its bounds are met. That said, after reading this doc it feels right to me to sequence these after the "implement supertrait via subtrait impl" approach, which is arguably more fundamental.
nikomatsakis: +1 to this being the more fundamental starting point
### scottmcm
As I said below, very interested in the problem space. I can't say that I feel confident about specifically what the solution ought to be.
### Josh
josh: :+1:, to both the underlying problem and the proposed solution. Also, much appreciation for pushing this forward! This has been needed for a long time, and I really enjoy when we can use language feature to help the ecosystem evolve and remove friction in the ecosystem.
### TC
Similarly, I'd like to solve this class of problems. In particular, I think this gives us a path to solving long-standing problems with relaxing bounds on associated types.
It's a big space, and I'm not settled yet on the right solution here. My initial vibe is that I don't immediately align with the perspective about implementable trait aliases being too surprising or that we may not want to do them due to having "two ways to do it".
On the question of "what's being implemented" versus "what's required to implement", I see that as something fundamental to the language that we should address head-on.
Niko mentioned he has thoughts on the semantics of how trait aliases should work; I'm curious to hear more about those.
Much more speculatively, with the prospective landing of the new trait solver, I wonder whether some kind of `pico_specialization` might come back on the table and have some applicability here.
### Jules
+1 to wanting to solve the problem, of course. The implementable aliases RFC is deliberately expansive, because that is what the lang team asked for following an earlier more narrow version—I’m not necessarily married to 100% of it getting into the MVP. For allowing subtrait impls to also impl supertraits, my big concern is accidentally implementing a trait without checking the conditions—but maybe the `auto` syntax that is being proposed would address that.
### ding
I would also love to have `trait` definition with an `impl` body.
## Meta: 100% agreed on the problem
scottmcm: Absolutely agreed that making trait splitting possible without semver breaks is a problem worth addressing :thumbsup:
tmandry: +1
## Splitting out `Clone::clone_from`
scottmcm: I just 2 days ago wrote another example of wanting something like this in <https://github.com/rust-lang/rfcs/pull/3437#discussion_r2136329759>. (I'll say that the exact shape I wrote doesn't necessarily need to be followed in terms of which traits there are, just the idea. If it were need need `CloneSelf`+`CloneFrom`+`Clone` or something that would be fine too.)
cramertj: inlining the example Scott wrote:
```rust=
trait Clone: Sized {
fn clone(&self) -> Self;
fn clone_from(&mut self, source: &Self) { *self = source.clone(); }
}
// becomes
trait Clone : Sized + CloneFrom<Self> {
fn clone(&self) -> Self;
}
trait CloneFrom<T: ?Sized> {
fn clone_from(&mut self, other: &T);
}
//But that needs some kind of
impl<T: Clone> CloneFrom<T> for T { ... }
//that I don't know how to write.
```
cramertj: note that implementing `Clone` would need to provide a default for `clone_from`, as if:
```rust=
trait Clone : Sized + auto CloneFrom<Self> {
fn CloneFrom::clone_from(&mut self, other: &T) {
*self = other.clone();
}
fn clone(&self) -> Self;
}
```
jules: yes, that part is something the implementable aliases RFC can’t address. We would need an additional feature
---
Scott: Brought another example of needing to implement a subtrait from a supertrait. I wanted something like this. Splitting out a provided method on a trait is more difficult than splitting out a required method.
---
Ding: Will there be a subsequent document with all the ideas and decisions discussed here?
Tyler: Depends on if someone wants to prepare one. If we can get aligned on the first step of solving this problem (it being implementable), the next step would be to defining a project goal.
TC: For Ding's perspective we need work for the arbitrary self types. We'll need to write those specific issues out and need an RFC for that. Something we won't be necessarily able to answer that in this meeting.
TC: We do want to solve the larger plane but it's also possible that we'll do a smaller piece, stabilize that and then address the rest later on. The next step is going to be someone writing an RFC for solving the `Deref`/`Receiver` case and maybe painting a picture of where we'll go.
Ding: I'd also like to take this opportunity to solve larger issues without blocking arbitrary self types too much.
Niko: The main two things we need are Deref/Receiver and the Service trait from Tower.
Taylor: Those two aren't blocked by the ambiguity.
Niko: There's always going to be ambiguity. I'd like note that whoever writes the RFC to resolve that ambiguity to talk to people about resolving .
Josh: The notion of implementing the supertrait with the providing default methods in subtrait for the supertrait. I'd like to defer that. Does anyone see door closing by shiping without it and adding it later?
Taylor: There is an ambiguity that even when the subtrait doesn't provide a default, if it's named you can't figure out whether or not from just looking at the impl whether it's supposed to provide impl for the supertrait or not.
Taylor: The most restrictive version for the Receiver and Tower Service problem is to name a supertrait item in a subtrait impl in order to get the supertrait.
Josh: As in the impl block of the subtrait?
Taylor: E.g. adding `type target=...`.
Taylor: If it's defaulted in any way (or many other examples) -- when the supertrait isn't mention, we can't say from just looking at an impl of the subtrait.
Josh: Trying to explore the question of: can we safely defer that without closing the door in the future.
Taylor: We should defer even more than that.
TC: My suggestion, Taylor, would be to scope it down to the things that don't have the ambiguities. Describe why that's a safe subset, talk about how we can extend it, how it doesn't close doors.
Josh: Taylor, are you saying you want to defer any case where you're marking auto any trait that that's a marker. I.e. you want to only mark as auto a trait that has at least one item.
Taylor: Yes and, any time you do not define in your impl an item in your supertrait, you should constrict it to only impls from the subtrait.
## `Foo + Send` and trait aliases and where-clauses
nikomatsakis: The text writes "Implementable trait aliases require syntax to distinguish between bounds that are required by the impl vs. bounds that are provided by the impl" but I think there is another alternative, though I'm not yet sure if I like like it. The idea that I considered was to say that `impl Send` (safe impl) is effectively an assertion that a type is `Send`. Therefore you can write the thing I believe I would want to write
```rust
trait LocalService {}
trait Service = LocalService<method(): Send> + Send;
// ^^ specifically I don't want to have where clauses on trait aliases, at least not to start,
// in part because I am not sure what bounds should be 'implied bounds' from traits.
```
and it would mean this
```rust
impl Service for MyNotSendType {} // ERROR
```
it would also be a useful thing to be able to assert in other cases, as today we have no way to ask the compiler to check that your type is `Send`.
cramertj: Does this only work for specifically `Send` / auto trait bounds? I can imagine wanting other bounds in a trait alias which would not be provided by the `impl` itself.
nikomatsakis: Yes, I imagined this as a generally useful behavior for Send/Sync -- or perhaps for (unsfae?) auto-traits in general. But the special-case-ness is definitely odd and it's what gives me pause.
cramertj: I can imagine other "marker trait"s that you wouldn't want to accidentally impl, even if they're safe.
nikomatsakis: +1
---
*Waiting for Niko.*
Niko: I'm aligned with the document that this is something we want and we can talk about this later.
## "The ambiguous/conflicting auto supertrait impl problem"
scottmcm: are there any ways we could put other restrictions on the traits involved, either automatically or opt-in? For example, if the split is happening involving only traits from the same crate then I think we might not need to worry as much about some of things because we'd have an opportunity to error on them immediately (like how `non_exhaustive` in the same crate is weaker). Something like `trait MyTrait : auto somecrate::Bar + auto othercrate::Foo` seems particularly scary but maybe also not so necessary so just making it disallowed might be fine?
josh: Do we need to go as far as "disallowed", or would linting on it be sufficient? It seems *useful* to be able to do, and I would expect it to sometimes happen with a cooperating set of crates, or for prototyping, or to effectively build a convenient implementable alias.
---
Tyler: I brought up a related question about the ambiguity, see the next section. At first I thought Taylor was saying that ?? one way to resolve the ambiguity
Taylor: The most important thing we're trying to fix is the semver/evolution problem. If you mention an item from a super trait, a lot of things break. You could force uniqueness in some way, but the ambiguity problem is tricky. Potential overlap of "does blanket impl apply here?". Getting very similar to the problems with the `Default` partial specification.
Scott: Is there anything that would help if we had marker trait `exist` on stable for the first cases of this?
Scott: We have the explicit opt-in for marker traits (empty trait that explictly doesn't have anything in). We have rules that say you don't get a marker trait and need to implement it specifically. Would that help or is irrelevant.
Taylor: This sounds like a different problem that Tyler and I were discussing?
Jules: Is the problem around clone_from?
Tyler: Assume you have a super trait with all provided items, unclear from the implementation of whether you need to do a specific impl from the supertrait and ??. If a subtrait can implement a supertrait but all the items are provided in the supertrait, it's not clear from just looking at the subtrait whether it's supposed to implement anything.
Jules: You could say that as long as the complete set of impls ends up consistent -- if you don't have a complete impl of a supertrait anyway -- then you can say don't use the one that's implicitly provided by the subtrait ipml.
Tyler: There are different trade-offs.
Jules: I think it's better to be explicit. There's a danger of people implementing traits without realising it.
Taylor: There's a conflict here between explicitness and semver evolvability.
Josh: The desire about splitting out a super trait to a subtrait. One case this really helps with: I already have a trait and I want to split supertrait with a subset of the items. Anything that requires you to change the subtrait impl of a block with any type would break the compatibility guarantee.
Jules: My RFC approach is: to split a trai like that, you'd have a new supertrait, a new subtrait and a new alias that combines the two. And the alias would have the name of the old trait. So the implementations of the old trait would become the implementations of the alias that combines the supertrait and supertrait. And you could also implement the new supertrait/subtrait.
Jules: E.g. in the Deref/Receiver case: Receiver is the new supertrait, Deref would be the alias and a new name e.g. DerefToReceiver which will be the subtrait. All impls of Deref would keep working (Receiver + DerefToReceiver) and people could write impl of just Receiver or just DerefToReceiver.
TC: That part of it -- the definition-side expressiveness I like a lot.
Tyler: I like that it's a very clear point on the design space. This could complicate if you're evolving traits multiple times. Not sure it's my favourite, but it's a clear way of resolving it. If we can't think of a better way to resolve the ambiguity problem, we could always do that.
Tyler: I'd love a solution that doesn't require you to pick a third name and doesn't have this history of the evolution.
Josh: I appreciate the clarity. I prefer the implementable subtrait approach because you don't have to give a coherent name to the version of the subtrait that's not implementable. See the "the thing that's kinda like Deref but not implementable" is not really straightforward to name (in previous Jules's example)
Jules: In this example all three traits could be implementable.
Josh: I meant it as the one that's not backwards compatible and unlikely for people will implement.
TC: The main thing I care about is the alias and the new supertrait. The subtrait being hard to name isn't that important to me; we could handle that in different ways like making it `#[doc(hidden)]`.
TC: Tyler, what's important is that you're requiring the same set of requirements and the same set of the impls. You can do this a number of ways (flattening, layering) so I don't worry about the multiple evolition issue that much. Plus unlikely people will do like 10 levels of evolution.
Jules: There are cases where people would name the e.g. `DerefToReceiver` when they're making blanket implementation.
Scott: Is this quadratic or exponential traits as we evolve?
Josh: If I have a trait, split out a new supertrait, I'll need three things. And if I need to split the supertrait again -- I'll need to end up with two extra for each split. It's linear but has a factor of two.
scottmcm: I wouldn't want to *suggest* this, but the thing that people should never implement directly could use the pub-trait-in-private-module hack so its name doesn't matter?
## Clarification on resolving the ambiguity when it's not clear if you want to use provided items of a supertrait in a subtrait definition
> In this case, I (cramertj@) believe that the ambiguity can always be resolved by either a definition of an item in the parent trait or the implementation of the parent trait must be equivalent.
tmandry: Should the last word have been "explicit"?
cramertj: No, what I was trying to say is that this:
```rust=
trait Marker {}
trait SubOne: auto Marker {}
trait SubTwo: auto Marker {}
impl SubOne for MyType {}
impl SubTwo for MyType {}
```
is unambiguous in that the impl of `Marker` is the same in both cases, so even though there are two impls, we could ignore the conflict.
tmandry: Okay. We could do that. Is there a slightly more conservative thing we could do that removes ambiguity?
## Handling markers that shouldn't be automatically implemented by implementing the subtrait
josh: Given *existing* uses of supertraits, we need to ensure that people don't get surprised by this as a new semver hazard. e.g. some existing trait has `trait LibraryTrait: LibraryMarkerTrait`, and expects people to have to `impl LibraryMarkerTrait`, but with this added, `impl LibraryTrait for Type` would automatically implement the marker trait.
cramertj: Yes, this is why I prefer an explicit opt-in via the use of a keyword (e.g. `trait Subtrait: auto Supertrait`).
josh: That definitely solves the problem. And at least it's opt-in at the point of trait definition, rather than opt-in at the point of implementation (which would be broken and not backwards-compatible). It seems worth briefly considering if there's any other way apart from an explicit opt-in that we could make this distinction. But I'm not seeing an obvious one. So, if nobody else does either, then :+1: for having an opt-in in the definition of the subtrait for each supertrait.
cramertj: Making it opt-in at the impl removes the semver benefit, yeah.
josh: Also, a new keyword would make this require an edition, or us finally shipping support for `k#` so you can write `k#auto`. I'm all for doing the latter, though. Or, is `auto` already a keyword and just isn't documented? It's used in the definition of traits like `Send`.
## blanket impl example incorrect
nikomatsakis: It's a nit, but a potentially important one, that this example is NOT equivalent to a `trait Subtrait = Supertrait` alias:
```rust
trait Supertrait {
// supertrait items
}
trait Subtrait {
// supertrait items + subtrait items
}
impl<T: Subtrait> Supertrait for T {
// impl supertrait items using subtrait items
}
```
...the blanket impl says that `Subtrait => Supertrait` (everything that is a subtrait is the supertrait) but not that `Supertrait => Subtrait` (everything that is the supertrait is the subtrait). You would need to do `trait Subtrait: Supertrait` to get the reverse direction.
cramertj: +1-- does the text above make that unclear? (or rather, how could I rephrase the text above to make it clearer?)
nikomatsakis: It did to me, in that we listed (1) add a supertrait and (2) add a bridge impl as two siblings in a list, but they have a very different impact (and neither does what I suspect is desired completely?). But perhaps it's more a function of the expectations I had coming in.
cramertj: Yes, the different solutions above have different impacts. I was viewing them as related ways of solving similar problems, and trying to contrast their effects. I agree that the blanket impl approach has the significant downside of not allowing you to infer that `Supertrait => Subtrait`.
## `LendingIterator` needs more
TC: This is a nit, but also maybe an important one. It's worth noting that we need more than this for `LendingIterator` to work.
```rust
pub trait LendingIterator {
type Item<'a> where Self: 'a;
fn next(&mut self) -> Option<Self::Item<'_>>;
}
pub trait Iterator = for<'a> LendingIterator<Item<'a> = <Self as Iterator>::Item> {
type Item;
}
```
The trouble is the interaction between the higher ranked binder and that `Self: 'a` item bound, which leads to the conclusion that the lifetime components of `Self` must outlive all possible lifetimes, i.e., essentially, that `Self: 'static`, but that's of course too onerous.
That's probably a bigger question that e.g. implementable trait aliases, but whatever we do for implementable trait aliases or auto impls or whatever should have whatever syntactic or other flexibility that we'll need to express the solution to this problem.
cramertj: Agreed, we don't actually have a way today to write `exists<T> for<'a> <Self as SomeTrait>::Assoc<'a>=T`. We definitely need some way to spell that bound. (in English: the associated type is not dependent upon its generic parameter).
ding: Specifically, but not the only perspective among others, is that we are missing a way to predicate on the binder `for<'a>` so that `Self: 'a`.
jules: another way of thinking about this (maybe not the best way), is that the `for<'a> <Self as LendingIterator>::Item<'a> …` bound could also match cases where `Self: 'static` does not necessarily hold, but the trait impl refines the `type Item` definition so as to not require `Self: 'a`.
ding: Digression: I have a feeling that very specifically for LendingIterator, a bridging type would help, which fills in the problematic universal lifetime binder. The bridge can look like
```rust
the_iterator.lend(): LeaseIter<'a>
```
where `LeaseIter<'a>` has a where bound to assert `the_iterator: 'a`.
Josh: (removed the use of the details HTML tag because it was breaking the highlighting of the rest of the document)
## Clarification on `auto` supertraits
nikomatsakis: In the following text, is `auto` meant to be a way of saying "`Supertrait` is an auto trait", or is it intended as actual Rust syntax (i.e., overloading the `auto` keyword to mean that, when you implement the subtrait, you also get an implementation of the supertrait)?
```rust
trait SubtraitOne: auto Supertrait {}
trait SubtraitTwo: auto Supertrait {}
```
If the former, then (at least presently) Supertrait would have no methods (though conceivably we could change that). If the latter, then the previous example in that section didn't use it but probably should've?
cramertj: I was using `auto` to mean that the following trait could be implemented via impls of the subtrait. This is not related to auto traits, and perhaps I could've picked a better keyword (naming is hard).
josh: `auto impl`, or `impl(auto)`, or similar? (Not advocating those, just brainstorming.)
josh: Different alternative, somewhat more verbose but definitively not ambiguous with the other meaning of "auto":
```rust
trait Subtrait: Supertrait {
impl Supertrait;
...
}
```
tmandry: When I first read this I thought you had typed..
```rust
impl Subtrait for () {
impl Supertrait;
}
```
josh: If I understand correctly what you're proposing, requiring that would break the property where splitting out a supertrait from a subtrait can be done without breaking compatibility. I was suggesting an alternative syntax instead of `auto`. I think this text ended up in the wrong place; it was a response to the comment that `auto` seemed ambiguous with the use of `auto` for defining traits like `Send`.
tmandry: +1, it would break that property.
## "Further possibility: subtrait provides defaults for supertrait items"
josh: I'd love to have this, but very much in favor of deferring it for now and coming back to it later, so that we can ship the base feature. Not a big fan of introducing a "diamond diagram" problem like this, for the default implementations. In particular, *adding* a default implementation of a method should never be a breaking change, because that would break a *different* kind of ecosystem evolution.
cramertj: Can you write an example explaining how you see adding a default implementation becoming a breaking change? Is the issue that an `auto` which previously did not apply suddenly begins applying and creates a conflict?
josh: Consider the exact example you gave above, where you have two subtraits with `auto Supertrait`, but only *one* has a default impl. If that code is allowed, then, adding a default impl to the other would become a breaking change.
cramertj: Here's an example:
```rust=
trait Supertrait {
fn item_without_default();
}
trait SubtraitOne: auto Supertrait {}
trait SubtraitTwo: auto Supertrait {
fn Supertrait::item_without_default() { im_the_other_default() }
}
// This is unambiguous, because only `SubtraitTwo` provides
// an implementation of the item without a default.
struct MyType;
impl SubtraitOne for MyType {}
impl SubtraitTwo for MyType {}
// If we change to
trait Supertrait {
fn item_without_default() { ... }
}
// Then the impls above are ambiguous: either can provide
// `Supertrait`. The first impl would use the default defined
// in `Supertrait`, while the second impl would use the default
// defined in `SubtraitTwo`.
```
cramertj: Josh, is this the ambiguity you had in mind? I agree that adding a default here causes a problem.
josh: That ambiguity doesn't inherently seem like a problem to me, in that I'd expect the default provided by `SubtraitTwo` to override the default in `Supertrait`. The ambiguity I was talking about was that modifying `SubtraitOne` to add a default for the item would be a breaking change, because it creates an ambiguity.
## `auto` unsafe traits
josh: We should define what happens if you try to `trait MyTrait: auto Send`. `Send` is an unsafe trait; it shouldn't be possible to imply an implementation of it without having `unsafe` somewhere. Either we should require `unsafe auto Send`, or we shouldn't allow `auto` of an unsafe trait at all. I'd propose the former.
cramertj: +1
jules: or, the impl needs to be `unsafe` if an `unsafe` trait is implemented, whether or not by `auto`
josh: We *could* require that, but I think it's reasonable to separate "discharge the obligation" from "add an obligation". While for `Send` I don't think it makes sense, I can easily imagine an `unsafe trait` for which you could write a safe `trait MySafeTrait: unsafe auto MyUnsafeTrait`.
josh: Actually, that nails down the exact requirement I'd specify: "You must use `unsafe impl` *if* you implement any methods/items of an unsafe supertrait." If you implement only items of the subtrait, and either the supertrait has no items *or* (in the future when the subtrait provides implementations of supertrait items) you don't override the default impls provided by the subtrait, you shouldn't need to write `unsafe impl`.
ding: +1. I personally would be surprise that an implicit `unsafe impl` registers no syntatical signal. I would rather have the compiler complaining about it at top of its voice.
## ambiguity when 2 traits have an item with the same name
jules: It’s a minor breaking change for traits to add items with defaults. Combined with allowing implementing 2 traits in 1 block, some method of disambiguating is necessary. My RFC addresses this with trait alias bodies; `: auto Supertrait` would need its own solution.
cramertj: I agree this is a problem. Can you help me write a specific example in code which demonstrates the issue?
```rust=
// from crate_1
trait Supertrait {
fn newly_added() { ... }
}
// from crate_2
trait Subtrait: Supertrait {
fn newly_added() { ... }
}
// from crate_downstream
trait Alias = Supertrait + Subtrait;
impl Alias for SomeType {
fn newly_added() { // what does this correspond to?
...
}
}
```
ding: Can we disambiguate by prepending a qualified path of the trait? I imagine it to be ...
fn Subtrait::newly_added() { .. }
fn Supertrait::newly_added() { .. }
... to compile.
jules: could work. In that case, the disambiguation happens in the impl block. (In my RFC, it happens in the alias definition.)
## Next Steps
Taylor: We want another RFC that specifically targets the two usecases (especially the RfL one, but also the Tower Service one) and that doesn't have the complexity/ambiguity that was brought up here.
Taylor: There's a pre-rfc for this already: [Supertrait associated items in subtrait impl](https://hackmd.io/@rust-for-linux-/SkucBLsWxl)
### Avoiding the ambiguity
cramertj: The thing I don't think we should support initially is:
```rust=
trait Super { fn super_with_default() {} }
trait Sub: auto Super {}
struct MyType;
impl Sub for MyType {}
```
or
```rust=
trait Super {}
trait Sub: auto Super {}
struct MyType;
impl Sub for MyType {}
```
because the impl doesn't define any items from the supertrait, so it's unclear whether the impl should act as an impl of the supertrait:
```rust=
struct MyType;
impl Super for MyType {} // <- this line could be added or removed
impl Sub for MyType {}
```
Without an item definition from the supertrait appearing in the impl of the subtrait, we can't tell whether there might also be a separate supertrait impl floating around.