Conversation
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.
… restructure scope sections
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;`: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Oh, neat! I did not realize this possibility existed. Sure, supporting an empty bound sounds reasonable.
| ### 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. |
There was a problem hiding this comment.
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 containDebugin the list. - The list of traits (same as above), must contain
Debug, or contain a subtrait ofDebug. - If the impl has a
trait Elem = ..... Then, the compiler checks whetherX: ....would implyX: Debugfor all typesX. This might hold if, say, we have a blanketimpl<X: ....> Debug for X.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Should something like dyn Container<Elem = Debug> be allowed?
| trait LocalPlugin { | ||
| fn run<T: 'static>(&self, task: T); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
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>;There was a problem hiding this comment.
I think this is similar to how HRTB can simulate #1598 (GATs). "... a hacky attempt to work around the limitation".
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.
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.