Skip to content

Tracking issue for trait aliases #41517

Open
0 of 1 issue completed
Open

Description

@withoutboats

View all comments

This is a tracking issue for the feature trait aliases (originally specified in RFC 1733).
The feature gate for the issue is #![feature(trait_alias)].

Important

The implementation of this feature is tracked separately in #55628.

About tracking issues

Tracking issues are used to record the overall progress of implementation.
They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

Steps

Unresolved questions

  • Should we really imply bounds on generic parameters other than the self type Self? Or rather, should we really imply bounds whose self type isn't Self or a projection thereof?
  • Should we keep the current behaviour of "shallow-banning" ?Sized bounds in trait objects, ban them "deeply" (through trait alias expansion), or permit them everywhere with no effect?
    • NOTE(fmease): The phrasing of that question is imprecise & confusing.
      1. First of all, ?Sized bounds in trait alias bounds makes no sense whatsoever since we don't perform Sized elaboration there (i.e., we don't implicitly add a Sized bound to trait aliases). For that reason PR RUST-147734 made them a hard error.
      2. Second of all, regarding "deep v shallow": Relaxed bounds like ?Sized don't get "inherited"; Sized elaboration happens in the relevant places without regard for any supertrait bounds
      3. Last of all, I wonder if that question actually meant to say "Should dyn EmptyTraitAlias be legal where trait EmptyTraitAlias =;?" In the current implementation, trait aliases get eagerly expanded inside trait object types and thus dyn EmptyTraitAlias ends up getting rejected just like dyn (sic!). I guess it could behave like trait EmptyTrait {} instead and thus get accepted. It's unclear if that's how one should interpret that question but it doesn't seem implausible.
  • Insufficient flexibility
  • Inconvenient syntax
  • Misleading naming
  • Which of constraint/bound/type aliases are desirable

Implementation history

See #55628 instead.

Activity

  1. added
    B-unstableBlocker: Implemented in the nightly compiler and unstable.
    B-RFC-approvedBlocker: Approved by a merged RFC but not yet implemented.
    T-langRelevant to the language team
    on Apr 24, 2017
  2. durka commented on May 7, 2017

    @durka
    Contributor

    I think #24010 (allowing aliases to set associated types) should be mentioned here.

  3. added a commit that references this issue on Jun 5, 2017
  4. added a commit that references this issue on Jul 7, 2017
  5. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Jul 22, 2017
  6. durka commented on Oct 2, 2017

    @durka
    Contributor

    I'd like to take a crack at this (starting with parsing).

  7. carllerche commented on Nov 3, 2017

    @carllerche
    Contributor

    I read the RFC and I saw a call out to Service, but I am not sure if the RFC actually solves the Service problem.

    Specifically, the "alias" needs to provide some additional associated types:

    See this snippet: https://gist.github.com/carllerche/76605b9f7c724a61a11224a36d29e023

    Basically, you rarely want to just alias HttpService to Service<Request = http::Request> You really want to do something like this (while making up syntax):

    trait HttpService = Service<http::Request<Self::RequestBody>> {
        type RequestBody;
    }

    In other words, the trait alias introduces a new associated type.

    The reason why you can't do: trait HttpService<B> = Service<http::Request<B>> is that then you end up getting into the "the type parameter B is not constrained by the impl trait, self type, or predicates" problem.

  8. hadronized commented on Nov 3, 2017

    @hadronized

    Basically, you rarely want to just alias HttpService to Service<Request = http::Request>

    Rarely? How do you define that?

    The syntax you suggest seems a bit complex to me and non-intuitive. I don’t get why we couldn’t make an exception in the way the “problem” shows up. Cannot we just hack around that rule you expressed? It’s not a “real trait”, it should be possible… right?

  9. carllerche commented on Nov 3, 2017

    @carllerche
    Contributor

    @phaazon rarely with regards to the service trait. This was not a general statement for when you would want trait aliasing.

    Also, the syntax was not meant to be a real proposal. It was only to illustrate what I was talking about.

  10. hadronized commented on Nov 3, 2017

    @hadronized

    I see. Cannot we just use free variables for that? Like, Service<Request = http::Request> implies the free variable used in http::request<_>?

  11. carllerche commented on Nov 3, 2017

    @carllerche
    Contributor

    @phaazon I don't understand this proposal.

  12. 132 remaining items

  13. Jules-Bertholet commented on Jun 1, 2025

    @Jules-Bertholet
    Contributor

    @johnazule Zulip could work, or you could also make a post on https://internals.rust-lang.org

  14. dotcarmen commented on Jun 10, 2025

    @dotcarmen

    It would be amazing if the derive macro was enhanced to use trait alias's aswell

    note that derive macros don't need to actually implement a trait, and can also override derive macros from std (see derive_more::Debug, for example). it would be neat to have, but i think it would be a footgun since now there's scoping questions (where should the derive macro be resolved? can trait aliases require deriving a non-trait derive? if so, how does that resolve on trait bounds since those traits aren't actually part of the type environment?)

  15. ijustlovemath commented on Jun 23, 2025

    @ijustlovemath

    It would be amazing if the derive macro was enhanced to use trait alias's aswell. Something along the lines of:

    trait NewTrait = Clone + Debug + PartialOrd;
    #[derive(NewTrait)]
    struct EgData(i32);

    I've come to this issue organically while trying to do a bit of refactoring. I'm curious what dissenters don't like about this. Is it too "DRYey"? Stylistically not good? Too much magic?

    In the case of scoping questions and orphan rule, is it possible to support fully qualified syntax to disambiguate when there is not a single clear implementer?

  16. jonaspleyer commented on Jun 23, 2025

    @jonaspleyer
    Contributor

    I think the most simple way forward is to have trait aliases and derive macro aliases as separate entities just as we have traits and derive macros. Any crate which uses or exposes them can now provide both. I believe it should be possible to have a syntax like so:

    derive_macro NewTrait = Trait1 + Trait2 + ...;

    Which when applied is syntactic sugar for

    #[derive(Trait1)]
    #[derive(Trait2)]
    ...
    struct MyStruct {...}

    In this way we should have the regular resolving rules.

    Edit: A very similar idea has been realized in this crate:
    https://docs.rs/macro_rules_attribute/latest/macro_rules_attribute/#derive-aliases

  17. added a commit that references this issue on Oct 30, 2025
  18. added a commit that references this issue on Oct 31, 2025
  19. added a commit that references this issue on Oct 31, 2025
  20. added
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    on Feb 14, 2026
  21. germaniuss commented on Oct 29, 2026

    @germaniuss

    Not sure if this is the right place to ask for this but associated trait aliases would be awesome as a future extension to this. It would allow to do things like this:

    struct Foo;
    trait Trait {
        trait Data;
        fn func<D: Self::Data>() -> usize;
    }
    
    trait Data {
        const N: usize;
    }
    
    impl Trait for Foo {
        trait Data = Data;
        fn func<D: Self::Data>() -> usize {
            D::N
        }
    }
    
    struct Consts;
    impl Data for Consts {
        const N: usize = 2;
    }
    
    Foo::func::<Consts>();

    Edit:
    After reading through the discussion I don't think this will ever be possible with thr current implementation as trait aliases are actually trait bound aliases.

  22. jdonszelmann commented on Nov 6, 2026

    @jdonszelmann
    Contributor

    This feature may interact poorly with implicit auto traits that are added implicitly to dyn trait objects. See for example tests/ui/traits/alias/effectively-empty-trait-object-type.rs which errors on empty trait aliases.

    // Test that we reject trait object types that effectively (i.e., after trait alias expansion)
    // don't contain any bounds.
    
    #![feature(trait_alias)]
    
    trait Empty0 =;
    
    // Nest a couple of levels deep:
    trait Empty1 = Empty0;
    trait Empty2 = Empty1;
    
    // Straight list expansion:
    type Type0 = dyn Empty2; //~ ERROR at least one trait is required for an object type [E0224]
    
    // Twice:
    trait Empty3 = Empty2 + Empty2;
    
    type Type1 = dyn Empty3; //~ ERROR at least one trait is required for an object type [E0224]
    
    fn main() {}
    

    dyn Empty3 wouldn't actually be empty if there's an implicit trait bound added such as Forget. However, it's still very unlikely the user wanted to write trait Empty0 =;

    Note, trait Empty0 = ?Forget; also does not work, erroring with a message "relaxed bounds are not permitted in trait alias bounds" which is fair enough, but again a poor interaction.

  23. fmease commented on Nov 6, 2026

    @fmease
    Member

    Note, trait Empty0 = ?Forget; also does not work, erroring with a message "relaxed bounds are not permitted in trait alias bounds" which is fair enough, but again a poor interaction.

    That's just a check that's perfectly fine to relax for new default traits like Move or Forget (I have a paragraph left in the relevant part of the compiler stating exactly that). We mainly reject relaxed bounds inside trait (alias) defs to guard against ?Sized as we don't perform Sized elaboration inside trait (alias) defs, so it would be meaningless to relax bounds there.

    It just doesn't talk about Sized specifically because Sized is the only stable default trait. If you enable feature more_maybe_bounds for example (which allows you to define off-the-shelf default auto traits), the message automatically adjusts itself to relaxed `Sized` bounds are not permitted in trait alias bounds.

  24. fmease commented on Nov 6, 2026

    @fmease
    Member

    Also note that we already add an implicit bound there if you write trait Trait =;, namely MetaSized. When lowering trait object types, we filter them out again to report empty trait object types w/o bounds in the surface language (admittedly though in quite a hacky fashion). Nothing insurmountable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-trait-systemArea: Trait systemB-RFC-approvedBlocker: Approved by a merged RFC but not yet implemented.B-unstableBlocker: Implemented in the nightly compiler and unstable.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCF-trait_alias`#![feature(trait_alias)]`S-tracking-needs-summaryStatus: It's hard to tell what's been done and what hasn't! Someone should do some investigation.T-langRelevant to the language teamT-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions