From 006a44ce80e7ee2bd2f9cfb4188929e8e18553e1 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Sat, 21 Mar 2026 13:25:42 +0800 Subject: [PATCH 1/9] RFC: Associated Traits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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/rfcs#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> --- text/0000-associated-traits.md | 489 +++++++++++++++++++++++++++++++++ 1 file changed, 489 insertions(+) create mode 100644 text/0000-associated-traits.md diff --git a/text/0000-associated-traits.md b/text/0000-associated-traits.md new file mode 100644 index 00000000000..521a1797869 --- /dev/null +++ b/text/0000-associated-traits.md @@ -0,0 +1,489 @@ +- Feature Name: `associated_traits` +- Start Date: 2026-03-21 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rfcs#2190](https://github.com/rust-lang/rfcs/issues/2190) + +## Summary +[summary]: #summary + +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 and the Rust equivalent of Haskell's `ConstraintKinds`. + +```rust +trait Container { + trait Elem; + fn process(&self, item: T); +} + +impl Container for MyContainer { + trait Elem = Send + Clone; + fn process(&self, item: T) { /* ... */ } +} +``` + +## Motivation +[motivation]: #motivation + +### The problem + +Rust programmers frequently need to write traits that are *generic over constraints*. Today, the only way to parameterize a trait over which bounds should apply to some type is to fix them at the trait definition site, or to duplicate the entire trait hierarchy for each set of constraints. + +Consider a plugin framework: + +```rust +// Without associated traits β€” constraints are baked in. +trait Plugin { + fn run(&self, task: T); +} +``` + +Every implementor must accept `Send + 'static`, even if some plugin systems (e.g., single-threaded ones) don't need `Send`. The only workaround today is to duplicate the trait: + +```rust +trait SendPlugin { + fn run(&self, task: T); +} + +trait LocalPlugin { + fn run(&self, task: T); +} +``` + +This duplication cascades through every consumer of the trait, every generic function, and every downstream crate. + +### Use cases from the community + +[rust-lang/rfcs#2190](https://github.com/rust-lang/rfcs/issues/2190) (76 πŸ‘, 12 πŸ‘€) has collected real-world use cases since 2017. The most prominent: + +**Async runtime agnosticism.** A runtime trait can abstract over whether futures must be `Send`: + +```rust +trait Runtime { + trait FutureConstraint; + fn spawn + Self::FutureConstraint>(f: F); +} + +impl Runtime for TokioRuntime { + trait FutureConstraint = Send + 'static; + // ... +} + +impl Runtime for LocalRuntime { + trait FutureConstraint = 'static; + // ... +} +``` + +Today, the async ecosystem is split between `Send` and `!Send` runtimes, requiring parallel trait hierarchies and duplicated generic code. + +**Type constructor families (higher-kinded types lite).** `PointerFamily` abstracts over `Arc` vs. `Rc` by parameterizing the element constraints: + +```rust +trait PointerFamily { + trait Bounds; + type Pointer; +} + +impl PointerFamily for ArcFamily { + trait Bounds = Send + Sync; + type Pointer = Arc; +} + +impl PointerFamily for RcFamily { + trait Bounds = Clone; + type Pointer = Rc; +} +``` + +**UI component frameworks.** An `Events` associated trait lets different component types declare which event traits are valid: + +```rust +trait Component { + type Props: Clone + 'static; + trait Events; + fn new(props: Self::Props) -> Self; +} +``` + +**Monitor/capability patterns.** A data structure constrains which kinds of monitors it accepts: + +```rust +trait DataStructure { + trait Mon: Monitor; +} +``` + +### Summary of benefits + +- Eliminates trait duplication when constraints vary per implementation. +- Composes naturally with existing features: associated types, GATs, trait inheritance, `impl Trait`, UFCS. +- Provides Rust's answer to Haskell's `ConstraintKinds` in an idiomatic, Rust-native way. +- Directly addresses a long-standing community request (2017–present, 76+ upvotes). + +## Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +### Declaring an associated trait + +Inside a trait body, you can declare an associated trait using the `trait` keyword, mirroring how `type` declares an associated type: + +```rust +trait Container { + trait Elem; +} +``` + +This says: "every implementation of `Container` must specify what `Elem` means as a trait bound." + +### Providing a value in an impl + +In an `impl` block, you provide the associated trait's value using `trait Name = TraitBound;`: + +```rust +impl Container for MyVec { + trait Elem = Send + Clone; +} +``` + +The value can be any valid trait bound: a single trait, a compound bound (`Send + Clone`), a trait with associated type constraints (`IntoIterator`), a lifetime bound (`'static`), or `?Sized`. + +### Using an associated trait as a bound + +Once declared, an associated trait can be used anywhere a regular trait bound is expected: + +```rust +fn process(container: &C, item: T) { + // T satisfies whatever Elem resolves to for this C. +} +``` + +This is the *primary use*: the caller sees `T: C::Elem`, and the trait solver resolves `C::Elem` to the concrete bounds from the impl (e.g., `Send + Clone` for `MyVec`). + +### Declaration bounds (supertraits) + +You can require that every implementation's value satisfies certain minimum bounds: + +```rust +trait Processor { + trait Constraint: Clone; // Every impl's value must include Clone +} + +impl Processor for MyProc { + trait Constraint = Clone + Send; // OK: Clone is satisfied +} + +impl Processor for BadProc { + trait Constraint = Send; // ERROR: does not satisfy Clone +} +``` + +### Defaults + +Associated traits can have defaults, just like associated types: + +```rust +trait Serializable { + trait Format = serde::Serialize; // Default; impls can override +} +``` + +### Generic associated traits + +Associated traits can have their own generic parameters, analogous to GATs: + +```rust +trait Transform { + trait Constraint; +} + +impl Transform for MyTransform { + trait Constraint = PartialEq; +} +``` + +Where clauses are also supported: + +```rust +trait Codec { + trait Decode where T: Send; +} +``` + +### UFCS disambiguation + +When a type parameter is bounded by multiple traits that each define an associated trait with the same name, you can use fully-qualified syntax to disambiguate: + +```rust +trait Readable { trait Constraint; } +trait Writable { trait Constraint; } + +fn transfer::Constraint>(data: R) {} +``` + +### `impl Trait` syntax + +Associated traits work with `impl Trait` in argument position: + +```rust +trait Handler { + trait Arg; + fn handle(&self, arg: impl Self::Arg); +} +``` + +### 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 β€” there is no type to make into a trait object. +- **Inherent impls**: `impl MyStruct { trait Foo = Send; }` is an error β€” associated traits only make sense in trait impls. + +### Error messages + +When the feature gate is absent: + +``` +error[E0658]: associated traits are experimental + --> src/lib.rs:3:5 + | +3 | trait Elem; + | ^^^^^^^^^^ + | + = note: see issue #99999 for more information + = help: add `#![feature(associated_traits)]` to the crate attributes +``` + +When an associated trait is used in type position: + +``` +error: expected type, found trait `Container::Elem` + --> src/lib.rs:10:14 + | +10 | let _x: T::Elem = todo!(); + | ^^^^^^^ not a type; `Elem` is an associated trait +``` + +## Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +### Syntax + +**Declaration** (in trait body): + +``` +trait Ident ; +trait Ident : Bounds ; +trait Ident = DefaultBounds ; +trait Ident : Bounds = DefaultBounds ; +trait Ident < GenericParams > ; +trait Ident < GenericParams > where WhereClauses ; +``` + +**Implementation** (in impl body): + +``` +trait Ident = Bounds ; +trait Ident < GenericParams > = Bounds ; +``` + +**Usage** (in bound position): + +``` +T: Container::Elem +T: ::Elem +T: C::Elem +``` + +### AST representation + +A new variant `AssocItemKind::Trait` is added to the AST, containing: +- `ident`: the name +- `generics`: generic parameters and where clauses +- `bounds`: declaration bounds (supertraits) +- `value`: the default or impl value (a list of `GenericBound`) +- `has_value`: whether a `= ...` value is present + +### HIR representation + +Two new variants: +- `TraitItemKind::Trait(GenericBounds)` β€” in trait definitions +- `ImplItemKind::Trait(GenericBounds)` β€” in trait implementations + +A new HIR bound variant: +- `GenericBound::AssocTraitBound(&Ty, &PathSegment, Span, Option)` β€” represents `B: C::Elem` in bound position, where the optional `DefId` carries UFCS trait disambiguation. + +### DefKind and middle representation + +- `DefKind::AssocTrait` β€” a new `DefKind` variant, distinct from `AssocTy`. +- `AssocKind::Trait { name: Symbol }` β€” a new `AssocKind` variant at the `ty` level. +- `AssocTag::Trait` β€” used to probe for associated trait items specifically. + +### Predicate + +A new clause kind is added: + +```rust +ClauseKind::AssocTraitBound(AssocTraitBoundPredicate { + self_ty: Ty, // The bounded type (B) + projection: AliasTerm, // The projection (::Elem) +}) +``` + +### Name resolution + +The resolver accepts partial resolutions (paths with unresolved trailing segments) in `PathSource::Trait(AliasPossibility::Maybe)` when the base is a type parameter, `Self` type param, or `Self` type alias. This allows `C::Elem` in bound position to resolve `C` as a type parameter and leave `Elem` as an unresolved associated item. + +For UFCS paths (`::Elem`), the resolver handles fully-qualified paths through `PathSource::TraitItem`, now extended to accept `DefKind::AssocTrait`. + +### Type checking + +**Projection**: Associated traits do *not* participate in type projection. `project()` returns `NoProgress` for `AssocTrait` projections. `type_of()` is unreachable (`span_bug!`) for associated traits. + +**Bound enforcement**: When `B: C::Elem` appears as a bound, the HIR type lowering emits a `ClauseKind::AssocTraitBound` predicate. The trait solver resolves this by: + +1. Using `selcx.select()` to find the impl for the container trait bound. +2. Using `assoc_def()` to locate the associated trait item in the impl. +3. Reading `explicit_item_bounds()` to get the concrete trait values. +4. Emitting `ClauseKind::Trait(B: ConcreteTraitValue)` obligations for each value trait. + +This means `Rc: C::Elem` is correctly rejected when `Elem = Send`. + +**Declaration bounds**: When a declaration has bounds (`trait Elem: Clone;`), `compare_impl_item` checks that the impl's value satisfies those supertraits. + +**UFCS disambiguation**: When the optional `constraint_trait` is present in `AssocTraitBound`, the bound resolution filters candidates to only the specified trait, correctly disambiguating `::Constraint` from `::Constraint`. + +### Interaction with other features + +**Associated types**: Associated traits and associated types coexist in the same trait body. They cannot share the same name (`E0325`). A trait can have both `type Item` and `trait Constraint`. An associated type can be bounded by an associated trait: `type Item: Self::Constraint;`. + +**GATs**: Associated traits can be declared alongside GATs and used to constrain GAT parameters at the use site, as in the `PointerFamily` pattern. + +**Trait inheritance**: Associated traits are inherited through supertraits. If `trait Base { trait Elem; }` and `trait Extended: Base { ... }`, then `Extended` inheritors can use `Self::Elem`. + +**`impl Trait`**: `impl C::Elem` in return position or argument position works through opaque type lowering, creating an opaque type bounded by the associated trait. + +**Cross-crate**: Associated trait declarations and values are available across crate boundaries. The metadata encodes `DefKind::AssocTrait` and `explicit_item_bounds` for associated trait items. + +**`dyn Trait`**: Associated traits are **not** object-safe. Using `dyn T::Elem` produces a specific error: "associated traits cannot be used with dyn". + +### Comparison with associated types + +| Aspect | Associated Type | Associated Trait | +|--------|----------------|-----------------| +| Declaration | `type Foo;` | `trait Foo;` | +| Value | `type Foo = i32;` | `trait Foo = Send;` | +| Position | Type position | Bound position | +| `type_of` | Returns the type | Unreachable (`span_bug!`) | +| Projection | Yes (`T::Foo` is a type) | No (`T::Foo` is a constraint) | +| DefKind | `AssocTy` | `AssocTrait` | +| Generics | Yes (GATs) | Yes (generic associated traits) | +| Defaults | Yes | Yes | +| UFCS | `::Foo` | `::Foo` | + +## Drawbacks +[drawbacks]: #drawbacks + +- **Language complexity**: This adds a new kind of associated item. Rust already has associated types, associated constants, and associated functions. A fourth category increases the surface area that all users, tools, and documentation must understand. + +- **Partial overlap with where clauses**: Some simple cases that associated traits address can be handled today via additional trait parameters or where clauses, though less ergonomically and without the ability to vary per implementation. + +- **Solver complexity**: The `AssocTraitBound` predicate adds a new resolution pathway in both the old and new trait solvers, which must be maintained alongside existing projection and trait obligation machinery. + +- **Bound expansion in method bodies**: Within an impl's method body, `T: Self::Arg` does not currently expand to the concrete bounds for the purposes of using methods from those bounds. The caller-side resolution works correctly, but within the impl body, users must add explicit bounds to use the expanded capabilities. This is a limitation that may be addressed in future work. + +## Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +### Why not reuse associated types? + +One might consider representing associated traits as associated types that happen to be traits (e.g., `type Elem: ?Sized = dyn Send;`). This was rejected for several reasons: + +1. **Semantic distinction**: Types and constraints are fundamentally different concepts. An associated type has a `type_of`, participates in type projection, and can appear in type position. An associated trait does none of these. Conflating them would require special-casing at every level. + +2. **Projection confusion**: `T::Elem` for an associated type returns a *type*. For an associated trait, it returns a *constraint*. Reusing the same mechanism would create ambiguity in the type system. + +3. **Proper infrastructure**: By giving associated traits their own `DefKind::AssocTrait`, `TraitItemKind::Trait`, and `ClauseKind::AssocTraitBound`, each compiler pass can handle them explicitly rather than routing through type machinery and failing at runtime. + +### Why not trait aliases? + +[Trait aliases](https://github.com/rust-lang/rfcs/pull/1733) (e.g., `trait SendClone = Send + Clone;`) are fixed at definition time. They cannot vary per implementation of an enclosing trait. Associated traits are the "associated" analog β€” they provide the same expressiveness that associated types provide over type aliases. + +### Why not additional generic parameters? + +Instead of `trait Container { trait Elem; }`, one could write `trait Container { ... }`. This makes `Elem` a *generic parameter*, not an *associated item*. The tradeoffs mirror those of generic parameters vs. associated types: + +- Generic parameters allow multiple implementations for the same type (e.g., `impl Container for Vec` and `impl Container for Vec`), which is usually not desired. +- Associated items enforce that each impl provides exactly one value, which is the common case. +- Associated items don't appear in the type signature at every use site. + +### Impact of not doing this + +Without associated traits, the Rust ecosystem will continue to duplicate trait hierarchies when constraints need to vary (as seen with async runtimes, serialization frameworks, and plugin systems). This duplication is a persistent source of boilerplate and a barrier to writing truly generic code. + +## Prior art +[prior-art]: #prior-art + +### Haskell: ConstraintKinds + +GHC's [`ConstraintKinds`](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/constraint_kind.html) extension allows constraints to be used as first-class kinds. A type family can return a constraint: + +```haskell +type family ElemConstraint (c :: * -> *) :: Constraint +type instance ElemConstraint MyVec = (Send, Clone) +``` + +This is more general than associated traits (constraints can appear in more positions), but Rust's associated traits achieve the most commonly needed subset β€” parameterizing traits over constraints β€” in a way that fits Rust's existing associated item pattern. + +### Haskell: RMonad + +The [`rmonad`](https://hackage.haskell.org/package/rmonad) library uses `ConstraintKinds` to define restricted monads, enabling data structures like `Set` (which requires `Ord`) to be monads. Associated traits in Rust would enable similar patterns: a `Family` trait with `trait Bounds` can restrict what types a type constructor accepts. + +### Scala: Abstract type members + +Scala's abstract type members serve a similar role to Rust's associated types, and Scala's type bounds can express constraint parameterization. However, Scala does not have a direct analog to "associated trait constraints" as a named, implementor-chosen bound. + +### Swift: Associated type constraints + +Swift's protocol associated types can have constraints (`associatedtype Element: Sendable`), but these are fixed at the protocol level β€” implementors cannot choose different constraints. Rust's associated traits go further by making the constraint itself the associated item. + +## Unresolved questions +[unresolved-questions]: #unresolved-questions + +### Before merging this RFC + +- **Bound expansion in method bodies**: Currently, within an impl method body, `T: Self::Arg` does not expand to allow calling methods from the concrete associated trait value. For example, if `trait Arg = IntoIterator`, a method body with `T: Self::Arg` cannot call `for x in arg` without an additional explicit `T: IntoIterator` bound. The bounds are correctly enforced at the *caller* side, but the impl body does not see the expanded constraints. Should this be considered a blocker, or is it acceptable as a known limitation for the initial feature? + +- **`impl Self::AssocTrait` in method arguments**: The OP's example includes `fn handle(&self, arg: impl Self::Arg)`. This works syntactically, but the impl body's view of `arg`'s capabilities is limited (see above). What user expectations should we set? + +- **Trait generic parameters**: The OP also proposes `fn foo where Impl: Trait { … }` β€” allowing trait-level generic parameters (not associated traits, but standalone trait parameters). This is a separate, more general feature. Should it be explicitly deferred, or should this RFC leave room for it? + +- **`where T::AssocTrait: OtherTrait` form**: Currently, `where T::Elem: Clone` is syntactically valid but semantically vacuous for associated traits (there is no type to bind `Clone` to). Should this be an error, a warning, or silently accepted? + +### Before stabilization + +- **Error codes**: Several associated-trait-specific error messages currently use ad-hoc `span_err` rather than dedicated error codes. Proper `EXXXX` codes should be assigned. + +- **Diagnostic quality**: Suggestions for common mistakes (e.g., using an associated trait in type position, forgetting to provide a value in an impl) should be polished. + +- **Incremental compilation**: Dedicated testing of incremental compilation scenarios with associated traits. + +- **New trait solver**: The implementation includes support in both the old and new trait solvers. The new solver support should be validated under `-Znext-solver`. + +### Out of scope for this RFC + +- **Negative associated trait bounds**: e.g., `trait Elem = !Send;`. This intersects with negative impls and is deferred. +- **`dyn`-compatible associated traits**: Making traits with associated traits object-safe. This requires significant design work around dynamic dispatch and is deferred. +- **Trait-level generic parameters**: `fn foo()` is a distinct and more general feature. + +## Future possibilities +[future-possibilities]: #future-possibilities + +- **Bound expansion in impl bodies**: A future enhancement could allow the compiler to automatically expand associated trait bounds within impl method bodies, so that `T: Self::Arg` where `Arg = IntoIterator` allows calling `IntoIterator` methods on `T` without an additional explicit bound. + +- **`where T::AssocTrait: OtherTrait`**: If the trait solver were extended to support "projection-level constraints on associated trait values," one could write `where T::Elem: Debug` to require that whatever `T::Elem` resolves to must include `Debug`. + +- **Trait-level generic parameters**: As proposed in the original issue, allowing `fn foo()` where `Trait` is a first-class trait parameter. This is the natural dual: associated traits are to trait aliases as associated types are to type aliases; trait parameters would be to generic type parameters as associated traits are to associated types. + +- **Higher-kinded types interaction**: As noted by several commenters on the original issue, associated traits compose well with HKT-like patterns. A `Family` trait with `trait Bounds` and `type Of` is a step toward restricted monads / restricted functors, similar to Haskell's `rmonad`. + +- **Non-lifetime HRTBs**: Combined with [non-lifetime higher-ranked trait bounds](https://github.com/rust-lang/rust/issues/108185) (`for Bar: Baz`), associated traits would enable significantly more expressive generic constraint patterns. + +- **Const associated traits**: By analogy with const generics, one could imagine associated traits that are const-evaluable, though the motivation for this is unclear. From c90ae2891fc0a6b96648ed878220abe90ddb11cd Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Wed, 25 Mar 2026 09:26:31 +0800 Subject: [PATCH 2/9] Clarify dyn limitation for associated traits 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. --- text/0000-associated-traits.md | 36 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/text/0000-associated-traits.md b/text/0000-associated-traits.md index 521a1797869..fb191705bed 100644 --- a/text/0000-associated-traits.md +++ b/text/0000-associated-traits.md @@ -9,6 +9,9 @@ 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 and the Rust equivalent of Haskell's `ConstraintKinds`. ```rust +#![feature(associated_traits)] +#![allow(incomplete_features)] + trait Container { trait Elem; fn process(&self, item: T); @@ -233,7 +236,7 @@ trait Handler { ### 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 β€” there is no type to make into a trait object. +- **`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. - **Inherent impls**: `impl MyStruct { trait Foo = Send; }` is an error β€” associated traits only make sense in trait impls. ### Error messages @@ -337,12 +340,17 @@ For UFCS paths (`::Elem`), the resolver handles fully-qualified path **Projection**: Associated traits do *not* participate in type projection. `project()` returns `NoProgress` for `AssocTrait` projections. `type_of()` is unreachable (`span_bug!`) for associated traits. -**Bound enforcement**: When `B: C::Elem` appears as a bound, the HIR type lowering emits a `ClauseKind::AssocTraitBound` predicate. The trait solver resolves this by: +**Solver support**: Associated traits are supported by both the old and new trait solvers. The old solver resolves `AssocTraitBound` predicates through its fulfillment engine (selecting the impl, extracting value bounds, emitting concrete trait obligations) and its evaluation path (mirroring the fulfillment logic for speculative queries). The new solver handles them via a dedicated `compute_assoc_trait_bound_goal` function. The feature is gated as `incomplete`, requiring both `#![feature(associated_traits)]` and `#![allow(incomplete_features)]`. + +**Bound enforcement**: When `B: C::Elem` appears as a bound, the HIR type lowering emits a `ClauseKind::AssocTraitBound` predicate. Both solver resolve this as follows: -1. Using `selcx.select()` to find the impl for the container trait bound. -2. Using `assoc_def()` to locate the associated trait item in the impl. -3. Reading `explicit_item_bounds()` to get the concrete trait values. -4. Emitting `ClauseKind::Trait(B: ConcreteTraitValue)` obligations for each value trait. +1. **Structurally normalize** the self-type of the projection's trait reference to determine whether the concrete impl is known. +2. **For abstract types** (type parameters, aliases, placeholders): add only the parent trait obligation (e.g., `C: Container`) and return success. The concrete value bounds cannot be resolved yet because the impl is unknown; declaration bounds are separately validated by `compare_impl_assoc_trait` at the impl site. +3. **For concrete types**: iterate over relevant impls matching the trait reference. For each candidate impl: + a. Probe the impl and unify the goal trait reference with the impl's trait reference. + b. Fetch the eligible associated trait item from the impl via `fetch_eligible_assoc_item`. + c. Read `item_bounds()` on the impl item to extract the concrete value traits. + d. For each value trait, emit a new `TraitPredicate` goal with the original self-type (e.g., `B: Send` if `Elem = Send`). This means `Rc: C::Elem` is correctly rejected when `Elem = Send`. @@ -362,7 +370,7 @@ This means `Rc: C::Elem` is correctly rejected when `Elem = Send`. **Cross-crate**: Associated trait declarations and values are available across crate boundaries. The metadata encodes `DefKind::AssocTrait` and `explicit_item_bounds` for associated trait items. -**`dyn Trait`**: Associated traits are **not** object-safe. Using `dyn T::Elem` produces a specific error: "associated traits cannot be used with dyn". +**`dyn Trait`**: Using an associated trait as a dyn bound (`dyn T::Elem`) is rejected because Rust type-checks generic bodies before monomorphization β€” the concrete trait set behind `T::Elem` is not yet known, and `dyn` requires a compile-time-known vtable layout. This is the same fundamental constraint that prevents `dyn T` where `T` is a type parameter. A trait that merely *has* associated traits can still be used as `dyn Trait` β€” the associated trait is simply unused in the dyn context. ### Comparison with associated types @@ -385,9 +393,9 @@ This means `Rc: C::Elem` is correctly rejected when `Elem = Send`. - **Partial overlap with where clauses**: Some simple cases that associated traits address can be handled today via additional trait parameters or where clauses, though less ergonomically and without the ability to vary per implementation. -- **Solver complexity**: The `AssocTraitBound` predicate adds a new resolution pathway in both the old and new trait solvers, which must be maintained alongside existing projection and trait obligation machinery. +- **Solver complexity**: The `AssocTraitBound` predicate adds a new resolution pathway in both the old and new trait solvers β€” the old solver through its fulfillment engine and evaluation path, the new solver via a dedicated `compute_assoc_trait_bound_goal` function. Both pathways must be maintained alongside existing projection and trait obligation machinery. -- **Bound expansion in method bodies**: Within an impl's method body, `T: Self::Arg` does not currently expand to the concrete bounds for the purposes of using methods from those bounds. The caller-side resolution works correctly, but within the impl body, users must add explicit bounds to use the expanded capabilities. This is a limitation that may be addressed in future work. +- **Incomplete feature status**: The feature is gated as `incomplete` rather than merely `unstable`, meaning the compiler emits a warning even when the feature is enabled. Users must also write `#![allow(incomplete_features)]` to suppress this warning. ## Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -449,9 +457,9 @@ Swift's protocol associated types can have constraints (`associatedtype Element: ### Before merging this RFC -- **Bound expansion in method bodies**: Currently, within an impl method body, `T: Self::Arg` does not expand to allow calling methods from the concrete associated trait value. For example, if `trait Arg = IntoIterator`, a method body with `T: Self::Arg` cannot call `for x in arg` without an additional explicit `T: IntoIterator` bound. The bounds are correctly enforced at the *caller* side, but the impl body does not see the expanded constraints. Should this be considered a blocker, or is it acceptable as a known limitation for the initial feature? +- **Bound expansion in method bodies**: ~~Currently, within an impl method body, `T: Self::Arg` does not expand to allow calling methods from the concrete associated trait value.~~ This has been implemented. When an impl provides `trait Arg = IntoIterator`, a method body with `T: Self::Arg` can now call `for x in arg` directly. The compiler expands `AssocTraitBound` predicates into concrete trait predicates in the method's parameter environment. -- **`impl Self::AssocTrait` in method arguments**: The OP's example includes `fn handle(&self, arg: impl Self::Arg)`. This works syntactically, but the impl body's view of `arg`'s capabilities is limited (see above). What user expectations should we set? +- **`impl Self::AssocTrait` in method arguments**: `fn handle(&self, arg: impl Self::Arg)` works correctly β€” the impl body can use the expanded capabilities of the associated trait value. - **Trait generic parameters**: The OP also proposes `fn foo where Impl: Trait { … }` β€” allowing trait-level generic parameters (not associated traits, but standalone trait parameters). This is a separate, more general feature. Should it be explicitly deferred, or should this RFC leave room for it? @@ -465,19 +473,17 @@ Swift's protocol associated types can have constraints (`associatedtype Element: - **Incremental compilation**: Dedicated testing of incremental compilation scenarios with associated traits. -- **New trait solver**: The implementation includes support in both the old and new trait solvers. The new solver support should be validated under `-Znext-solver`. +- **Solver validation**: Both the old and new solver implementations should be validated across a broad range of patterns, including cross-crate scenarios, before stabilization. ### Out of scope for this RFC - **Negative associated trait bounds**: e.g., `trait Elem = !Send;`. This intersects with negative impls and is deferred. -- **`dyn`-compatible associated traits**: Making traits with associated traits object-safe. This requires significant design work around dynamic dispatch and is deferred. +- **`dyn` with associated traits**: `dyn T::Elem` is not supported because Rust's type system requires dyn vtable layouts to be known at type-checking time, before monomorphization resolves `T`. This is the same constraint that prevents `dyn T` for type parameters. A trait with associated traits can still be used as `dyn Trait`; only the associated trait itself cannot appear as a dyn bound. - **Trait-level generic parameters**: `fn foo()` is a distinct and more general feature. ## Future possibilities [future-possibilities]: #future-possibilities -- **Bound expansion in impl bodies**: A future enhancement could allow the compiler to automatically expand associated trait bounds within impl method bodies, so that `T: Self::Arg` where `Arg = IntoIterator` allows calling `IntoIterator` methods on `T` without an additional explicit bound. - - **`where T::AssocTrait: OtherTrait`**: If the trait solver were extended to support "projection-level constraints on associated trait values," one could write `where T::Elem: Debug` to require that whatever `T::Elem` resolves to must include `Debug`. - **Trait-level generic parameters**: As proposed in the original issue, allowing `fn foo()` where `Trait` is a first-class trait parameter. This is the natural dual: associated traits are to trait aliases as associated types are to type aliases; trait parameters would be to generic type parameters as associated traits are to associated types. From c3f393c585c96f0b08c24af466aa6019bf8c5a00 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Wed, 25 Mar 2026 11:22:33 +0800 Subject: [PATCH 3/9] Remove incomplete feature status from associated_traits --- text/0000-associated-traits.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/text/0000-associated-traits.md b/text/0000-associated-traits.md index fb191705bed..e91e5c87675 100644 --- a/text/0000-associated-traits.md +++ b/text/0000-associated-traits.md @@ -10,7 +10,6 @@ Allow traits to declare **associated traits** β€” named trait constraints that a ```rust #![feature(associated_traits)] -#![allow(incomplete_features)] trait Container { trait Elem; @@ -340,7 +339,7 @@ For UFCS paths (`::Elem`), the resolver handles fully-qualified path **Projection**: Associated traits do *not* participate in type projection. `project()` returns `NoProgress` for `AssocTrait` projections. `type_of()` is unreachable (`span_bug!`) for associated traits. -**Solver support**: Associated traits are supported by both the old and new trait solvers. The old solver resolves `AssocTraitBound` predicates through its fulfillment engine (selecting the impl, extracting value bounds, emitting concrete trait obligations) and its evaluation path (mirroring the fulfillment logic for speculative queries). The new solver handles them via a dedicated `compute_assoc_trait_bound_goal` function. The feature is gated as `incomplete`, requiring both `#![feature(associated_traits)]` and `#![allow(incomplete_features)]`. +**Solver support**: Associated traits are supported by both the old and new trait solvers. The old solver resolves `AssocTraitBound` predicates through its fulfillment engine (selecting the impl, extracting value bounds, emitting concrete trait obligations) and its evaluation path (mirroring the fulfillment logic for speculative queries). The new solver handles them via a dedicated `compute_assoc_trait_bound_goal` function. The feature is gated as `unstable`, requiring `#![feature(associated_traits)]`. **Bound enforcement**: When `B: C::Elem` appears as a bound, the HIR type lowering emits a `ClauseKind::AssocTraitBound` predicate. Both solver resolve this as follows: @@ -395,8 +394,6 @@ This means `Rc: C::Elem` is correctly rejected when `Elem = Send`. - **Solver complexity**: The `AssocTraitBound` predicate adds a new resolution pathway in both the old and new trait solvers β€” the old solver through its fulfillment engine and evaluation path, the new solver via a dedicated `compute_assoc_trait_bound_goal` function. Both pathways must be maintained alongside existing projection and trait obligation machinery. -- **Incomplete feature status**: The feature is gated as `incomplete` rather than merely `unstable`, meaning the compiler emits a warning even when the feature is enabled. Users must also write `#![allow(incomplete_features)]` to suppress this warning. - ## Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives From e0d3d301f00ce128185358b81f201c63fa6e78ab Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Wed, 25 Mar 2026 11:24:44 +0800 Subject: [PATCH 4/9] Remove resolved items from Unresolved questions Bound expansion in method bodies and impl Self::AssocTrait in method arguments are both implemented. Remove from unresolved questions. --- text/0000-associated-traits.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/text/0000-associated-traits.md b/text/0000-associated-traits.md index e91e5c87675..1edbbb83485 100644 --- a/text/0000-associated-traits.md +++ b/text/0000-associated-traits.md @@ -454,10 +454,6 @@ Swift's protocol associated types can have constraints (`associatedtype Element: ### Before merging this RFC -- **Bound expansion in method bodies**: ~~Currently, within an impl method body, `T: Self::Arg` does not expand to allow calling methods from the concrete associated trait value.~~ This has been implemented. When an impl provides `trait Arg = IntoIterator`, a method body with `T: Self::Arg` can now call `for x in arg` directly. The compiler expands `AssocTraitBound` predicates into concrete trait predicates in the method's parameter environment. - -- **`impl Self::AssocTrait` in method arguments**: `fn handle(&self, arg: impl Self::Arg)` works correctly β€” the impl body can use the expanded capabilities of the associated trait value. - - **Trait generic parameters**: The OP also proposes `fn foo where Impl: Trait { … }` β€” allowing trait-level generic parameters (not associated traits, but standalone trait parameters). This is a separate, more general feature. Should it be explicitly deferred, or should this RFC leave room for it? - **`where T::AssocTrait: OtherTrait` form**: Currently, `where T::Elem: Clone` is syntactically valid but semantically vacuous for associated traits (there is no type to bind `Clone` to). Should this be an error, a warning, or silently accepted? From c064518cd80ca028a3889b4a80fea6a75612c843 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Wed, 25 Mar 2026 12:50:40 +0800 Subject: [PATCH 5/9] Document call-site value constraints (where C::Elem: Debug) Move from unresolved questions and future possibilities to implemented features. Add guide-level documentation. --- text/0000-associated-traits.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/text/0000-associated-traits.md b/text/0000-associated-traits.md index 1edbbb83485..ed3a9b23fec 100644 --- a/text/0000-associated-traits.md +++ b/text/0000-associated-traits.md @@ -232,6 +232,21 @@ trait Handler { } ``` +### Call-site value constraints + +You can constrain an associated trait's value at the call site using `where` clause syntax: + +```rust +fn print_element(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. + ### Where associated traits *cannot* appear - **Type position**: `let x: T::Elem = ...` is an error β€” associated traits are constraints, not types. @@ -456,8 +471,6 @@ Swift's protocol associated types can have constraints (`associatedtype Element: - **Trait generic parameters**: The OP also proposes `fn foo where Impl: Trait { … }` β€” allowing trait-level generic parameters (not associated traits, but standalone trait parameters). This is a separate, more general feature. Should it be explicitly deferred, or should this RFC leave room for it? -- **`where T::AssocTrait: OtherTrait` form**: Currently, `where T::Elem: Clone` is syntactically valid but semantically vacuous for associated traits (there is no type to bind `Clone` to). Should this be an error, a warning, or silently accepted? - ### Before stabilization - **Error codes**: Several associated-trait-specific error messages currently use ad-hoc `span_err` rather than dedicated error codes. Proper `EXXXX` codes should be assigned. @@ -477,8 +490,6 @@ Swift's protocol associated types can have constraints (`associatedtype Element: ## Future possibilities [future-possibilities]: #future-possibilities -- **`where T::AssocTrait: OtherTrait`**: If the trait solver were extended to support "projection-level constraints on associated trait values," one could write `where T::Elem: Debug` to require that whatever `T::Elem` resolves to must include `Debug`. - - **Trait-level generic parameters**: As proposed in the original issue, allowing `fn foo()` where `Trait` is a first-class trait parameter. This is the natural dual: associated traits are to trait aliases as associated types are to type aliases; trait parameters would be to generic type parameters as associated traits are to associated types. - **Higher-kinded types interaction**: As noted by several commenters on the original issue, associated traits compose well with HKT-like patterns. A `Family` trait with `trait Bounds` and `type Of` is a step toward restricted monads / restricted functors, similar to Haskell's `rmonad`. From bd3482e93d0e42635a2e99a5ab3f5c65a59fb331 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Wed, 25 Mar 2026 14:23:27 +0800 Subject: [PATCH 6/9] Revise associated traits RFC: improve summary examples, fix defaults, restructure scope sections --- text/0000-associated-traits.md | 80 ++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/text/0000-associated-traits.md b/text/0000-associated-traits.md index ed3a9b23fec..ca9a5a5f7a7 100644 --- a/text/0000-associated-traits.md +++ b/text/0000-associated-traits.md @@ -6,20 +6,34 @@ ## Summary [summary]: #summary -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 and the Rust equivalent of Haskell's `ConstraintKinds`. +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. ```rust #![feature(associated_traits)] +// 1. Declare an associated trait in a trait definition. trait Container { trait Elem; fn process(&self, item: T); } +// 2. Each impl chooses the concrete constraints. impl Container for MyContainer { trait Elem = Send + Clone; fn process(&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(container: &C, item: T) { + container.process(item); +} + +// 4. Fully-qualified syntax also works. +fn process_qualified::Elem>(container: &C, item: T) { + container.process(item); +} ``` ## Motivation @@ -117,8 +131,7 @@ trait DataStructure { ### Summary of benefits - Eliminates trait duplication when constraints vary per implementation. -- Composes naturally with existing features: associated types, GATs, trait inheritance, `impl Trait`, UFCS. -- Provides Rust's answer to Haskell's `ConstraintKinds` in an idiomatic, Rust-native way. +- Composes naturally with existing features: associated types, generic associated types, trait inheritance, `impl Trait`, UFCS. - Directly addresses a long-standing community request (2017–present, 76+ upvotes). ## Guide-level explanation @@ -183,14 +196,26 @@ impl Processor for BadProc { Associated traits can have defaults, just like associated types: ```rust -trait Serializable { - trait Format = serde::Serialize; // Default; impls can override +trait Runtime { + trait FutureConstraint = Send; // Default; most runtimes want Send + fn spawn + Self::FutureConstraint>(f: F); +} + +// TokioRuntime is happy with the default (Send). +impl Runtime for TokioRuntime { + fn spawn + Self::FutureConstraint>(f: F) { /* ... */ } +} + +// A single-threaded runtime overrides the default. +impl Runtime for LocalRuntime { + trait FutureConstraint = 'static; // No Send requirement + fn spawn + Self::FutureConstraint>(f: F) { /* ... */ } } ``` ### Generic associated traits -Associated traits can have their own generic parameters, analogous to GATs: +Associated traits can have their own generic parameters, analogous to generic associated types: ```rust trait Transform { @@ -376,7 +401,7 @@ This means `Rc: C::Elem` is correctly rejected when `Elem = Send`. **Associated types**: Associated traits and associated types coexist in the same trait body. They cannot share the same name (`E0325`). A trait can have both `type Item` and `trait Constraint`. An associated type can be bounded by an associated trait: `type Item: Self::Constraint;`. -**GATs**: Associated traits can be declared alongside GATs and used to constrain GAT parameters at the use site, as in the `PointerFamily` pattern. +**Generic associated types**: Associated traits can be declared alongside generic associated types and used to constrain their parameters at the use site, as in the `PointerFamily` pattern. **Trait inheritance**: Associated traits are inherited through supertraits. If `trait Base { trait Elem; }` and `trait Extended: Base { ... }`, then `Extended` inheritors can use `Self::Elem`. @@ -396,7 +421,7 @@ This means `Rc: C::Elem` is correctly rejected when `Elem = Send`. | `type_of` | Returns the type | Unreachable (`span_bug!`) | | Projection | Yes (`T::Foo` is a type) | No (`T::Foo` is a constraint) | | DefKind | `AssocTy` | `AssocTrait` | -| Generics | Yes (GATs) | Yes (generic associated traits) | +| Generics | Yes (generic associated types) | Yes (generic associated traits) | | Defaults | Yes | Yes | | UFCS | `::Foo` | `::Foo` | @@ -412,16 +437,6 @@ This means `Rc: C::Elem` is correctly rejected when `Elem = Send`. ## Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -### Why not reuse associated types? - -One might consider representing associated traits as associated types that happen to be traits (e.g., `type Elem: ?Sized = dyn Send;`). This was rejected for several reasons: - -1. **Semantic distinction**: Types and constraints are fundamentally different concepts. An associated type has a `type_of`, participates in type projection, and can appear in type position. An associated trait does none of these. Conflating them would require special-casing at every level. - -2. **Projection confusion**: `T::Elem` for an associated type returns a *type*. For an associated trait, it returns a *constraint*. Reusing the same mechanism would create ambiguity in the type system. - -3. **Proper infrastructure**: By giving associated traits their own `DefKind::AssocTrait`, `TraitItemKind::Trait`, and `ClauseKind::AssocTraitBound`, each compiler pass can handle them explicitly rather than routing through type machinery and failing at runtime. - ### Why not trait aliases? [Trait aliases](https://github.com/rust-lang/rfcs/pull/1733) (e.g., `trait SendClone = Send + Clone;`) are fixed at definition time. They cannot vary per implementation of an enclosing trait. Associated traits are the "associated" analog β€” they provide the same expressiveness that associated types provide over type aliases. @@ -464,33 +479,22 @@ Scala's abstract type members serve a similar role to Rust's associated types, a Swift's protocol associated types can have constraints (`associatedtype Element: Sendable`), but these are fixed at the protocol level β€” implementors cannot choose different constraints. Rust's associated traits go further by making the constraint itself the associated item. -## Unresolved questions -[unresolved-questions]: #unresolved-questions - -### Before merging this RFC - -- **Trait generic parameters**: The OP also proposes `fn foo where Impl: Trait { … }` β€” allowing trait-level generic parameters (not associated traits, but standalone trait parameters). This is a separate, more general feature. Should it be explicitly deferred, or should this RFC leave room for it? - -### Before stabilization - -- **Error codes**: Several associated-trait-specific error messages currently use ad-hoc `span_err` rather than dedicated error codes. Proper `EXXXX` codes should be assigned. - -- **Diagnostic quality**: Suggestions for common mistakes (e.g., using an associated trait in type position, forgetting to provide a value in an impl) should be polished. - -- **Incremental compilation**: Dedicated testing of incremental compilation scenarios with associated traits. - -- **Solver validation**: Both the old and new solver implementations should be validated across a broad range of patterns, including cross-crate scenarios, before stabilization. - -### Out of scope for this RFC +## Out of scope +[out-of-scope]: #out-of-scope - **Negative associated trait bounds**: e.g., `trait Elem = !Send;`. This intersects with negative impls and is deferred. - **`dyn` with associated traits**: `dyn T::Elem` is not supported because Rust's type system requires dyn vtable layouts to be known at type-checking time, before monomorphization resolves `T`. This is the same constraint that prevents `dyn T` for type parameters. A trait with associated traits can still be used as `dyn Trait`; only the associated trait itself cannot appear as a dyn bound. -- **Trait-level generic parameters**: `fn foo()` is a distinct and more general feature. +- **Trait-level generic parameters**: `fn foo()` β€” allowing traits as first-class generic parameters β€” is a distinct and more general feature. It is the natural dual: associated traits are to trait aliases as associated types are to type aliases; trait parameters would be to generic type parameters as associated traits are to associated types. + +## Unresolved questions +[unresolved-questions]: #unresolved-questions + +None at this time. ## Future possibilities [future-possibilities]: #future-possibilities -- **Trait-level generic parameters**: As proposed in the original issue, allowing `fn foo()` where `Trait` is a first-class trait parameter. This is the natural dual: associated traits are to trait aliases as associated types are to type aliases; trait parameters would be to generic type parameters as associated traits are to associated types. +- **Trait-level generic parameters**: As proposed in the original issue, allowing `fn foo()` where `Trait` is a first-class generic parameter (see [Out of scope](#out-of-scope)). This would complement associated traits by enabling fully generic constraint parameterization at call sites. - **Higher-kinded types interaction**: As noted by several commenters on the original issue, associated traits compose well with HKT-like patterns. A `Family` trait with `trait Bounds` and `type Of` is a step toward restricted monads / restricted functors, similar to Haskell's `rmonad`. From 053fa6f6ae9d58386be87d12a99d20e70a9d5983 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Sat, 28 Mar 2026 10:31:31 +0800 Subject: [PATCH 7/9] Update header --- text/0000-associated-traits.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-associated-traits.md b/text/0000-associated-traits.md index ca9a5a5f7a7..e29a8cebb1a 100644 --- a/text/0000-associated-traits.md +++ b/text/0000-associated-traits.md @@ -1,6 +1,6 @@ - Feature Name: `associated_traits` -- Start Date: 2026-03-21 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Start Date: 2026-03-28 +- RFC PR: [rust-lang/rfcs#3938](https://github.com/rust-lang/rfcs/pull/3938) - Rust Issue: [rust-lang/rfcs#2190](https://github.com/rust-lang/rfcs/issues/2190) ## Summary From 48d56ec6c67d040390c2cd98b2269cc2046fde35 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Sat, 28 Mar 2026 10:36:04 +0800 Subject: [PATCH 8/9] Remove misleading link in header --- text/0000-associated-traits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-associated-traits.md b/text/0000-associated-traits.md index e29a8cebb1a..907b6896df8 100644 --- a/text/0000-associated-traits.md +++ b/text/0000-associated-traits.md @@ -1,7 +1,7 @@ - Feature Name: `associated_traits` - Start Date: 2026-03-28 - RFC PR: [rust-lang/rfcs#3938](https://github.com/rust-lang/rfcs/pull/3938) -- Rust Issue: [rust-lang/rfcs#2190](https://github.com/rust-lang/rfcs/issues/2190) +- Rust Issue: None ## Summary [summary]: #summary From a2764ef14120abd1017f93f99d8edaef1ad858cb Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Sat, 28 Mar 2026 19:34:08 +0800 Subject: [PATCH 9/9] Rewrite reference-level explanation to focus on language semantics 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> --- text/0000-associated-traits.md | 251 ++++++++++++++++++++++++--------- 1 file changed, 183 insertions(+), 68 deletions(-) diff --git a/text/0000-associated-traits.md b/text/0000-associated-traits.md index 907b6896df8..9e5002b93a7 100644 --- a/text/0000-associated-traits.md +++ b/text/0000-associated-traits.md @@ -306,110 +306,227 @@ error: expected type, found trait `Container::Elem` ## Reference-level explanation [reference-level-explanation]: #reference-level-explanation -### Syntax +### Associated trait declarations -**Declaration** (in trait body): +A trait definition may contain **associated trait declarations**, introduced with the `trait` keyword. An associated trait declaration names a trait constraint whose concrete value is provided by each impl. + +**Grammar** (in trait body): ``` -trait Ident ; -trait Ident : Bounds ; -trait Ident = DefaultBounds ; -trait Ident : Bounds = DefaultBounds ; -trait Ident < GenericParams > ; -trait Ident < GenericParams > where WhereClauses ; +AssocTraitDecl = + "trait" IDENT ";" + | "trait" IDENT ":" Bounds ";" + | "trait" IDENT "=" Bounds ";" + | "trait" IDENT ":" Bounds "=" Bounds ";" + | "trait" IDENT GenericParams ";" + | "trait" IDENT GenericParams WhereClause ";" + | "trait" IDENT GenericParams ":" Bounds ";" + | "trait" IDENT GenericParams WhereClause ":" Bounds "=" Bounds ";" ``` -**Implementation** (in impl body): +Examples: +```rust +trait Container { + trait Elem; // bare declaration + trait Constraint: Clone; // with supertrait bound + trait Format = Send; // with default value + trait Handler: Debug; // generic + trait Codec where T: Send; // generic with where clause +} ``` -trait Ident = Bounds ; -trait Ident < GenericParams > = Bounds ; + +**Declaration bounds** (the part after `:`) are *requirements* on every impl's value. If `trait Elem: Clone;`, then every impl must provide a value that implies `Clone`. These bounds are also available to callers: a function with `T: C::Elem` may assume `T: Clone` when the declaration includes `: Clone`. + +**Defaults** (the part after `=`) provide a value that impls may omit, analogous to default associated types. If an impl omits the associated trait, the default is used. + +### Associated trait implementations + +In a trait impl, the associated trait's value is provided with `trait Name = Bounds;`: + +```rust +impl Container for MyVec { + trait Elem = Send + Clone; +} ``` -**Usage** (in bound position): +The value may be: +- One or more trait bounds: `Send`, `Clone + Debug` +- Trait bounds with associated type constraints: `IntoIterator` +- Lifetime bounds: `'static`, `Send + 'static` +- Relaxed bounds: `?Sized`, `Send + ?Sized` +- Any combination of the above: `Debug + Send + 'static` + +**Validation**: The compiler checks that the impl's value satisfies the declaration's supertrait bounds. If the declaration says `trait Elem: Clone;`, the impl value must include `Clone` (or a subtrait of `Clone`). +Associated traits are **not permitted in inherent impls** β€” only in trait impls: + +```rust +impl MyStruct { + trait Foo = Send; // ERROR: not allowed in inherent impls +} ``` -T: Container::Elem -T: ::Elem -T: C::Elem + +### Using associated traits as bounds + +An associated trait may appear anywhere a trait bound is expected, using the path syntax `C::Elem`: + +```rust +fn process(item: T) { ... } ``` -### AST representation +This means: "`T` must satisfy whatever constraint `C::Elem` resolves to." In a generic context, the concrete value is not yet known, but any declaration bounds (supertraits) are available: -A new variant `AssocItemKind::Trait` is added to the AST, containing: -- `ident`: the name -- `generics`: generic parameters and where clauses -- `bounds`: declaration bounds (supertraits) -- `value`: the default or impl value (a list of `GenericBound`) -- `has_value`: whether a `= ...` value is present +```rust +trait Container { + trait Elem: Debug + 'static; +} + +// T: Debug and T: 'static are available here from the declaration +fn to_debug(item: T) -> Box { + Box::new(item) // OK: T: Debug + 'static from declaration bounds +} +``` + +At a concrete call site (e.g., `process::(42)`), the solver resolves `MyVec`'s impl of `Container`, extracts `Elem = Send + Clone`, and verifies `i32: Send + Clone`. -### HIR representation +Associated traits may also appear in: -Two new variants: -- `TraitItemKind::Trait(GenericBounds)` β€” in trait definitions -- `ImplItemKind::Trait(GenericBounds)` β€” in trait implementations +- **Where clauses**: `where T: C::Elem` +- **Inline bounds**: `fn foo()` +- **`impl Trait`**: `fn foo(arg: impl C::Elem)` and `fn foo() -> impl C::Elem` +- **Combined with other bounds**: `T: C::Elem + PartialEq` -A new HIR bound variant: -- `GenericBound::AssocTraitBound(&Ty, &PathSegment, Span, Option)` β€” represents `B: C::Elem` in bound position, where the optional `DefId` carries UFCS trait disambiguation. +### Fully-qualified (UFCS) syntax -### DefKind and middle representation +When a type parameter is bounded by multiple traits that each declare an associated trait with the same name, the shorthand `T::Elem` is ambiguous. Fully-qualified syntax resolves the ambiguity: -- `DefKind::AssocTrait` β€” a new `DefKind` variant, distinct from `AssocTy`. -- `AssocKind::Trait { name: Symbol }` β€” a new `AssocKind` variant at the `ty` level. -- `AssocTag::Trait` β€” used to probe for associated trait items specifically. +```rust +trait Readable { trait Constraint; } +trait Writable { trait Constraint; } + +fn transfer::Constraint, + W: ::Constraint>(r: R, w: W) { ... } +``` -### Predicate +The syntax `::AssocTrait` mirrors the existing UFCS syntax for associated types (`::AssocType`). -A new clause kind is added: +### Positions where associated traits are rejected + +Associated traits are constraints, not types. They are rejected in positions that expect a type: + +- **Type position**: `let x: T::Elem = ...;` β†’ error: "expected type, found trait" +- **Return type**: `fn foo() -> T::Elem` β†’ error (unless `impl T::Elem`) +- **Struct fields**: `struct S { field: T::Elem }` β†’ error +- **`dyn` position**: `dyn T::Elem` β†’ error: "associated traits cannot be used with dyn" + +The `dyn` restriction exists because `dyn` requires a compile-time-known vtable layout, and in a generic context the set of traits behind `T::Elem` is not yet known. This is the same fundamental constraint that prevents `dyn T` where `T` is a type parameter. + +A trait that *has* associated traits can still be used as `dyn Trait` β€” the associated trait is simply inaccessible in the dyn context. + +### Generic associated traits + +Associated traits may have their own generic parameters, analogous to generic associated types (GATs): ```rust -ClauseKind::AssocTraitBound(AssocTraitBoundPredicate { - self_ty: Ty, // The bounded type (B) - projection: AliasTerm, // The projection (::Elem) -}) +trait Transform { + trait Constraint; +} + +impl Transform for MyTransform { + trait Constraint = PartialEq + Debug; +} +``` + +Generic parameters may include types, lifetimes, and bounds. Where clauses are also supported: + +```rust +trait Codec { + trait Decode<'a, T> where T: Send; +} +``` + +Usage includes the generic arguments: + +```rust +fn decode>() { ... } +``` + +### Interaction with associated types + +Associated traits and associated types may coexist in the same trait. They occupy the same name namespace β€” a trait cannot have both `type Foo` and `trait Foo` with the same name. + +An associated type may be bounded by an associated trait from the same trait: + +```rust +trait Container { + trait ElemConstraint; + type Elem: Self::ElemConstraint; +} ``` -### Name resolution +### Interaction with generic associated types -The resolver accepts partial resolutions (paths with unresolved trailing segments) in `PathSource::Trait(AliasPossibility::Maybe)` when the base is a type parameter, `Self` type param, or `Self` type alias. This allows `C::Elem` in bound position to resolve `C` as a type parameter and leave `Elem` as an unresolved associated item. +Associated traits compose with GATs. An associated trait can constrain the parameters of a GAT at the use site: -For UFCS paths (`::Elem`), the resolver handles fully-qualified paths through `PathSource::TraitItem`, now extended to accept `DefKind::AssocTrait`. +```rust +trait PointerFamily { + trait Bounds; + type Pointer; +} -### Type checking +fn wrap(val: T) -> F::Pointer { ... } +``` -**Projection**: Associated traits do *not* participate in type projection. `project()` returns `NoProgress` for `AssocTrait` projections. `type_of()` is unreachable (`span_bug!`) for associated traits. +GAT parameters may also be directly bounded by associated traits: -**Solver support**: Associated traits are supported by both the old and new trait solvers. The old solver resolves `AssocTraitBound` predicates through its fulfillment engine (selecting the impl, extracting value bounds, emitting concrete trait obligations) and its evaluation path (mirroring the fulfillment logic for speculative queries). The new solver handles them via a dedicated `compute_assoc_trait_bound_goal` function. The feature is gated as `unstable`, requiring `#![feature(associated_traits)]`. +```rust +trait Universe { + trait BoundsIn; + trait BoundsOut; + type Ref: RefLike; + type Cell: CellLike; +} +``` -**Bound enforcement**: When `B: C::Elem` appears as a bound, the HIR type lowering emits a `ClauseKind::AssocTraitBound` predicate. Both solver resolve this as follows: +When a GAT parameter is bounded by `Self::Bounds` and used in an impl, the compiler substitutes the concrete associated trait value to check that the impl's GAT definition satisfies its requirements. No additional `where` clauses are required on downstream types that use the GAT β€” bounds on GAT parameters are checked at instantiation. -1. **Structurally normalize** the self-type of the projection's trait reference to determine whether the concrete impl is known. -2. **For abstract types** (type parameters, aliases, placeholders): add only the parent trait obligation (e.g., `C: Container`) and return success. The concrete value bounds cannot be resolved yet because the impl is unknown; declaration bounds are separately validated by `compare_impl_assoc_trait` at the impl site. -3. **For concrete types**: iterate over relevant impls matching the trait reference. For each candidate impl: - a. Probe the impl and unify the goal trait reference with the impl's trait reference. - b. Fetch the eligible associated trait item from the impl via `fetch_eligible_assoc_item`. - c. Read `item_bounds()` on the impl item to extract the concrete value traits. - d. For each value trait, emit a new `TraitPredicate` goal with the original self-type (e.g., `B: Send` if `Elem = Send`). +### Interaction with trait inheritance -This means `Rc: C::Elem` is correctly rejected when `Elem = Send`. +Associated traits are inherited through supertraits: -**Declaration bounds**: When a declaration has bounds (`trait Elem: Clone;`), `compare_impl_item` checks that the impl's value satisfies those supertraits. +```rust +trait Base { trait Elem; } +trait Extended: Base { trait Extra; } -**UFCS disambiguation**: When the optional `constraint_trait` is present in `AssocTraitBound`, the bound resolution filters candidates to only the specified trait, correctly disambiguating `::Constraint` from `::Constraint`. +fn use_both() { ... } +``` -### Interaction with other features +UFCS can disambiguate inherited associated traits: -**Associated types**: Associated traits and associated types coexist in the same trait body. They cannot share the same name (`E0325`). A trait can have both `type Item` and `trait Constraint`. An associated type can be bounded by an associated trait: `type Item: Self::Constraint;`. +```rust +fn use_base::Elem>() { ... } +``` + +### Interaction with `impl Trait` -**Generic associated types**: Associated traits can be declared alongside generic associated types and used to constrain their parameters at the use site, as in the `PointerFamily` pattern. +`impl C::Elem` works in both argument and return position, creating an opaque type bounded by the associated trait: + +```rust +trait Handler { + trait Arg; + fn handle(&self, arg: impl Self::Arg); +} +``` -**Trait inheritance**: Associated traits are inherited through supertraits. If `trait Base { trait Elem; }` and `trait Extended: Base { ... }`, then `Extended` inheritors can use `Self::Elem`. +### Cross-crate usage -**`impl Trait`**: `impl C::Elem` in return position or argument position works through opaque type lowering, creating an opaque type bounded by the associated trait. +Associated trait declarations and their values are visible across crate boundaries. A crate can define a trait with associated traits, and downstream crates can implement and use them. -**Cross-crate**: Associated trait declarations and values are available across crate boundaries. The metadata encodes `DefKind::AssocTrait` and `explicit_item_bounds` for associated trait items. +### Auto-trait interaction -**`dyn Trait`**: Using an associated trait as a dyn bound (`dyn T::Elem`) is rejected because Rust type-checks generic bodies before monomorphization β€” the concrete trait set behind `T::Elem` is not yet known, and `dyn` requires a compile-time-known vtable layout. This is the same fundamental constraint that prevents `dyn T` where `T` is a type parameter. A trait that merely *has* associated traits can still be used as `dyn Trait` β€” the associated trait is simply unused in the dyn context. +The concrete types used with associated traits participate in auto-trait inference normally. For example, if `Universe::Ref` is `Arc`, then a struct containing `U::Ref>` is `Send + Sync` when `U` is `Shared` (with `Arc>`), and `!Send` when `U` is `Isolated` (with `Rc>`). ### Comparison with associated types @@ -417,13 +534,11 @@ This means `Rc: C::Elem` is correctly rejected when `Elem = Send`. |--------|----------------|-----------------| | Declaration | `type Foo;` | `trait Foo;` | | Value | `type Foo = i32;` | `trait Foo = Send;` | -| Position | Type position | Bound position | -| `type_of` | Returns the type | Unreachable (`span_bug!`) | -| Projection | Yes (`T::Foo` is a type) | No (`T::Foo` is a constraint) | -| DefKind | `AssocTy` | `AssocTrait` | -| Generics | Yes (generic associated types) | Yes (generic associated traits) | +| Valid positions | Type position | Bound position | +| Generics | Yes (GATs) | Yes (generic associated traits) | | Defaults | Yes | Yes | | UFCS | `::Foo` | `::Foo` | +| `dyn` compatible | Yes (with limitations) | No | ## Drawbacks [drawbacks]: #drawbacks @@ -432,7 +547,7 @@ This means `Rc: C::Elem` is correctly rejected when `Elem = Send`. - **Partial overlap with where clauses**: Some simple cases that associated traits address can be handled today via additional trait parameters or where clauses, though less ergonomically and without the ability to vary per implementation. -- **Solver complexity**: The `AssocTraitBound` predicate adds a new resolution pathway in both the old and new trait solvers β€” the old solver through its fulfillment engine and evaluation path, the new solver via a dedicated `compute_assoc_trait_bound_goal` function. Both pathways must be maintained alongside existing projection and trait obligation machinery. +- **Solver complexity**: Associated traits introduce a new kind of predicate that both the old and new trait solvers must handle, adding to the compiler's internal complexity. However, the resolution logic follows established patterns from associated type projection. ## Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives