Skip to content

RFC: Implementable trait aliases #3437

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 45 commits into
base: master
Choose a base branch
from

Conversation

Jules-Bertholet
Copy link
Contributor

@Jules-Bertholet Jules-Bertholet commented May 24, 2023

Rendered

Allow writing impl blocks for most trait aliases. Also, allow trait aliases to have bodies.

Prior discussion on Internals

@rustbot label A-traits

@rustbot rustbot added the A-traits Trait system related proposals & ideas label May 24, 2023
@ehuss ehuss added the T-lang Relevant to the language team, which will review and decide on the RFC. label May 25, 2023
- 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one benefit of requiring #[implementable] is that it mitigates the confusing aspect of:

#[implementable] // error: must only have one trait on rhs of equal -- generated because of #[implementable]
pub trait Foo = Bar + Baz;
// vs.
#[implementable]
pub trait Foo = Bar
where
    Self: Baz;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added some more discussion about this.

@burdges
Copy link

burdges commented May 25, 2023

As an aside, it'd be nice if type aliases permitted instantiation:

pub struct FooT<T>([T; 32]);
pub type Bar = Foo<u8>;

impl Default for Bar {
    fn default() -> Bar {
        Bar( Default::default() )   // Forbidden right now.
    }
} 

@programmerjake
Copy link
Member

programmerjake commented May 25, 2023

As an aside, it'd be nice if type aliases permitted instantiation:

they do, if you use curly braces (except for tuples):
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b95fe39281037ac567cfc8dfa7aba44b

i think it'd also be nice to allow curly brace syntax for tuples for ease of writing proc macros so there's one consistent syntax that works on all struct-like types:

type T<A, B> = (A, B);
let v = T::<_, _> { 0: "abc", 1: 123 };
match v {
    T::<_, _> { 0: a, 1: b } => println!("it works! a={a} b={b}"),
}

@dlight
Copy link

dlight commented May 26, 2023

they do, if you use curly braces (except for tuples):
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b95fe39281037ac567cfc8dfa7aba44b

Wait, so T { 0: 5 } works but T(5) doesn't? This sounds like a bug.

@Jules-Bertholet
Copy link
Contributor Author

Jules-Bertholet commented May 26, 2023

Wait, so T { 0: 5 } works but T(5) doesn't? This sounds like a bug.

Changing this behavior would be a breaking change, for code like the following:

struct Foo();
type Bar = Foo;
fn Bar() {}

Anyway, this is all a bit off-topic for the RFC 😁

@nielsle
Copy link

nielsle commented May 28, 2023

I think that the RFC should mention #1672 and mutually exclusive traits

rust-lang/rust#20400
https://geo-ant.github.io/blog/2021/mutually-exclusive-traits-rust/
https://stackoverflow.com/questions/57749827/mutually-exclusive-traits

Is this RFC an alternative to #1672 or is it orthogonal?

@Jules-Bertholet
Copy link
Contributor Author

@nielsle I don't see any relationship between this RFC and that issue.

@tmandry
Copy link
Member

tmandry commented Sep 7, 2023

I think the summary motivation section should be fleshed out more, ideally with real-world examples (possibly simplified). After looking through the text this does seem useful but it wasn't obvious at all to me what the feature was from those sections.

@Jules-Bertholet
Copy link
Contributor Author

I've specified that implementable trait aliases also support fully-qualified method call syntax.

@nikomatsakis
Copy link
Contributor

@rustbot +I-lang-nominates +I-types-nominated

I'm nominating this for @rust-lang/lang and @rust-lang/types discussion. I myself am in favor of this RFC. I've seen a lot of demand for a simplified version of trait aliases where--

(A) We only support trait Foo = Bar + Baz format, essentially.
(B) The trait Foo is implementable and, in general, acts like a "normal trait" so that users don't have to know about the more detailed version.

I expect this to be relevant to async fn in traits as well (cc @tmandry) it's quite common to have something like trait Service (which requires Send) and trait LocalService (which does not).

Restricting to point (A) has the advantage of avoiding some complex corner cases. Note though that I do want to support the "inline bound" syntax, so that you could do trait SendIterator = Iterator<Item: Send> (as well as RTN if we adopt that), so that trait aliases can put bounds on associated types.

@nikomatsakis
Copy link
Contributor

I guess that my question to the lang/types teams, respectively, are:

  1. Lang: what do people think about the motivation here as well as the more limited proposal? Are there important trait alias use cases not covered there?
  2. Types: are there concerns about the limited proposal that I put forward?

@nikomatsakis
Copy link
Contributor

One though -- @Jules-Bertholet -- I'm not sure if the RFC covers it, I didn't have time to read in super detail, but I think that if you are implementing trait Foo = Bar + Baz, that implies "unioning" the methods from Bar and Baz into one impl block. Does the RFC discuss this?

I have, for a long time, wanted the ability to implement a trait and its supertraits together in a single impl block, I wonder if it's worth thinking about that as well, though I'd probably want to separate it out from this RFC.

@fmease fmease added I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. I-types-nominated Indicates that an issue has been nominated for prioritizing at the next types team meeting. labels Sep 16, 2023
@Jules-Bertholet
Copy link
Contributor Author

One though -- @Jules-Bertholet -- I'm not sure if the RFC covers it, I didn't have time to read in super detail, but I think that if you are implementing trait Foo = Bar + Baz, that implies "unioning" the methods from Bar and Baz into one impl block. Does the RFC discuss this?

The RFC does not do any sort of trait unioning like this, I hadn't even considered it as a possibility (I will add it to the alternatives section). Notably, you would need to handle name collisions.

@Jules-Bertholet
Copy link
Contributor Author

I've added text to the alternatives section addressing @nikomatsakis's "unioning" idea.

@nikomatsakis
Copy link
Contributor

@Jules-Bertholet

Notably, you would need to handle name collisions.

Yes. Presumably that would be an error.

@Jules-Bertholet
Copy link
Contributor Author

Presumably that would be an error.

That would have backward-compatibility implications, if one of the traits adds a defaulted method that conflicts. Technically "minor" breakage, but still not ideal.

@Jules-Bertholet
Copy link
Contributor Author

I’ve added a small note to the future possibilities, connecting this to const traits. I will try to discuss target features as well, but need some time to think about that first.

@Jules-Bertholet
Copy link
Contributor Author

I’ve now realized that this feature is actually sufficient to address the “make LendingIterator a supertrait of Iterator” issue on its own. I’ve moved that out of the future possibilities and into the main body of the RFC.

@traviscross
Copy link
Contributor

I’ve now realized that this feature is actually sufficient to address the “make LendingIterator a supertrait of Iterator” issue on its own. I’ve moved that out of the future possibilities and into the main body of the RFC.

cc @compiler-errors

@Jules-Bertholet
Copy link
Contributor Author

I will try to discuss target features as well, but need some time to think about that first.

This has now been added to the future possibilities.

@Jules-Bertholet Jules-Bertholet force-pushed the implementable-trait-alias branch from c1bc691 to 5415e73 Compare June 5, 2025 19:05
@Jules-Bertholet
Copy link
Contributor Author

I’ve removed the implementability restrictions on type and const alias items. They just added pointless complexity.

@Jules-Bertholet
Copy link
Contributor Author

It's worth mentioning that we're also talking about allowing impls that cover both a subtrait and supertrait, as described here:

I’ve added a section on why I believe making this “just work” in general would be a bad idea, and why the extra hoops that this RFC requires are necessary.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing that I've been wishing:

Today we have

trait Clone: Sized {
    fn clone(&self) -> Self;
    fn clone_from(&mut self, source: &Self) { *self = source.clone(); }
}

It would be really nice to be able to split this in two without breaking semver, something like

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.

Is that something that this RFC would solve? I think lots of the "I want to split a trait" would need something like this.

Copy link
Contributor Author

@Jules-Bertholet Jules-Bertholet Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it would not, because of the default impl of clone_from in terms of clone, which the mechanisms in this RFC wouldn’t be able to preserve. However, I have a post on Internals from 2 months ago, that would offer a solution. Combining this RFC and that post’s features, you could do:

trait CloneInto: CloneFrom<Self> {
    fn clone(&self) -> Self;
}

trait CloneFrom<T: ?Sized> {
    fn clone_from(&mut self, other: &T);
}

// Implementable trait alias, from this RFC
trait Clone = CloneInto + CloneFrom;

// default partial impl, from the Internals post
default partial impl<T: CloneInto> CloneFrom<Self> for T {
     fn clone_from(&mut self, source: &Self) { *self = source.clone(); }
}

@Jules-Bertholet
Copy link
Contributor Author

I should mention: this RFC in its present state is deliberately very expansive. The initial version was much more narrow and minimal, but the feedback from the lang team was that they wanted to see something that covered more use-cases. Therefore, in the latest version, I’ve intentionally biased in the opposite direction, addressing as many use-cases as possible, with only minimal concern for ease of implementation or keeping things simple. In particular, some of the more advanced features of trait alias bodies could perhaps be excluded from the MVP (though leaving alias bodies out entirely would be a mistake, as I explain here).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-traits Trait system related proposals & ideas I-lang-radar Items that are on lang's radar and will need eventual work or consideration. I-types-nominated Indicates that an issue has been nominated for prioritizing at the next types team meeting. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.