Skip to content

RFC: Associated traits#3938

Open
sandersaares wants to merge 9 commits intorust-lang:masterfrom
sandersaares:associated-traits-rfc
Open

RFC: Associated traits#3938
sandersaares wants to merge 9 commits intorust-lang:masterfrom
sandersaares:associated-traits-rfc

Conversation

@sandersaares
Copy link
Copy Markdown

@sandersaares sandersaares commented Mar 28, 2026

Allow traits to declare associated traits — named trait constraints that are defined abstractly in a trait and given concrete values in implementations. Just as associated types let a trait abstract over which type is used, associated traits let a trait abstract over which constraints are imposed. This is the trait-level analog of associated types.

#![feature(associated_traits)]

// 1. Declare an associated trait in a trait definition.
trait Container {
    trait Elem;
    fn process<T: Self::Elem>(&self, item: T);
}

// 2. Each impl chooses the concrete constraints.
impl Container for MyContainer {
    trait Elem = Send + Clone;
    fn process<T: Self::Elem>(&self, item: T) { /* ... */ }
}

// 3. Generic code outside the impl uses `C::Elem` as a bound.
//    This is the primary use case: the caller is generic over
//    the constraints without knowing what they are.
fn process_item<C: Container, T: C::Elem>(container: &C, item: T) {
    container.process(item);
}

// 4. Fully-qualified syntax also works.
fn process_qualified<C: Container, T: <C as Container>::Elem>(container: &C, item: T) {
    container.process(item);
}

Previous discussion in #2190.

Prototype implementation: https://github.com/sandersaares/rust/tree/associated-traits

Rendered

Important

When responding to RFCs, try to use inline review comments (it is possible to leave an inline review comment for the entire file at the top) instead of direct comments for normal comments and keep normal comments for procedural matters like starting FCPs.

This keeps the discussion more organized.

sandersaares and others added 7 commits March 21, 2026 13:25
Proposes associated traits — named trait constraints declared
abstractly in a trait and given concrete values in implementations.
This is the trait-level analog of associated types and the Rust
equivalent of Haskell's ConstraintKinds.

Based on rust-lang#2190 (76+ upvotes, 2017-present) and a
working prototype implementation on the rust-lang/rust associated-traits
branch with 57 tests and full UI suite passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dyn T::Elem is not supported because Rust type-checks generic bodies
before monomorphization, so the vtable layout (which depends on T)
is not yet known. Remove dyn binding syntax from future possibilities.
Bound expansion in method bodies and impl Self::AssocTrait in method
arguments are both implemented. Remove from unresolved questions.
Move from unresolved questions and future possibilities to
implemented features. Add guide-level documentation.
@fmease fmease added T-types Relevant to the types team, which will review and decide on the RFC. T-lang Relevant to the language team, which will review and decide on the RFC. labels Mar 28, 2026
Replace implementation details (AST variants, HIR representations,
DefKind, solver internals) with language-level reference material:
- Associated trait declarations (grammar, declaration bounds, defaults)
- Associated trait implementations (values, validation, restrictions)
- Usage as bounds (shorthand, UFCS, where clauses, impl Trait)
- Positions where rejected (type, dyn, inherent impl)
- Generic associated traits
- Interaction with associated types, GATs, trait inheritance, auto-traits
- Cross-crate usage
- Comparison table

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

### Associated trait implementations

In a trait impl, the associated trait's value is provided with `trait Name = Bounds;`:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm curious whether this intends to support "no additional bounds". For example https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=c5774b6a2c62aa4352b5f87ac941f3d0 where the two functions show you can have a where clause with an empty bound. See the where clause section in the reference https://doc.rust-lang.org/reference/items/generics.html#grammar-WhereClause . The syntax you supplied does not consider the Bound optional. Maybe it's helpful to add a dedicated grammar section here too?

It seems less useful to me in the example I provided, but I can imagine wanting to write a test-only trait impl without any additional bounds on the associated trait.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Oh, neat! I did not realize this possibility existed. Sure, supporting an empty bound sounds reasonable.

Comment on lines +260 to +273
### Call-site value constraints

You can constrain an associated trait's value at the call site using `where` clause syntax:

```rust
fn print_element<C: Container, T: C::Elem>(x: T)
where
C::Elem: Debug, // The impl's value for Elem must include Debug
{
println!("{:?}", x);
}
```

This is different from `T: C::Elem + Debug` (which constrains `T` independently). The value constraint `C::Elem: Debug` constrains the *Container's impl* — if the impl provides `trait Elem = Send` (no Debug), the call is rejected even if `T` happens to implement Debug.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It's not clear to me what C::Elem: Debug means. Here are some possible interpretations:

  • The list of traits in the trait Elem = .... declaration in the impl, must literally contain Debug in the list.
  • The list of traits (same as above), must contain Debug, or contain a subtrait of Debug.
  • If the impl has a trait Elem = ..... Then, the compiler checks whether X: .... would imply X: Debug for all types X. This might hold if, say, we have a blanket impl<X: ....> Debug for X.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Also, it's not clear to me why we would want only this direction of constraints. For example, why wouldn't we want to write something like where Trait: C::Elem? Such a bound would, for example, maybe allow something like:

trait Consumer {
    trait Requirements;
    fn consume<T: Self::Requirements>(&self, value: T);
}

fn consume_debug<C: Consumer>(c: C)
where
    Debug: C::Requirements
{
    // c can consume anything that implements Debug
    c.consume(1);
    c.consume("abc");
}

### Where associated traits *cannot* appear

- **Type position**: `let x: T::Elem = ...` is an error — associated traits are constraints, not types.
- **`dyn` position**: `dyn T::Elem` is an error. Rust type-checks generic function bodies once, before monomorphization, so the set of traits behind `T::Elem` is not yet known. Since `dyn` types require a compile-time-known vtable layout, `dyn` with an associated trait that depends on a type parameter is fundamentally incompatible with Rust's generics model. This is the same reason `dyn T` where `T` is a type parameter is rejected.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should something like dyn Container<Elem = Debug> be allowed?

trait LocalPlugin {
fn run<T: 'static>(&self, task: T);
}
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

To what degree can HRTB/non_lifetime_binders replace this feature? E.g.

trait Plugin<T> {
    fn run(&self, task: T);
}

trait SendPlugin = for<T: Send + 'static> Plugin<T>;
trait LocalPlugin = for<T: 'static> Plugin<T>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this is similar to how HRTB can simulate #1598 (GATs). "... a hacky attempt to work around the limitation".

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

Labels

T-lang Relevant to the language team, which will review and decide on the RFC. T-types Relevant to the types team, which will review and decide on the RFC.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants