Default Associated Types
Status: Approved Approved: 2026-01-31 Author: Claude Created: 2026-01-31 Depends On: None Enables: operator-traits-proposal.md (with default-type-parameters-proposal.md) Parallel With: default-type-parameters-proposal.md
Summary
Allow associated types in traits to have default values, enabling type Output = Self where implementors can omit the associated type if the default is acceptable.
Motivation
Without default associated types, every impl must specify all associated types even when the default would suffice:
// Today: verbose
impl Vector2: Add {
type Output = Vector2 // Required even though it's always Self
@add (self, rhs: Vector2) -> Vector2 = ...
}
// Goal: omit when default applies
impl Vector2: Add {
@add (self, rhs: Vector2) -> Self = ... // Output defaults to Self
}
Most operator implementations return Self. Requiring explicit type Output = Self in every impl is boilerplate.
Design
Syntax
Default associated types use = Type after the type name:
trait Add<Rhs = Self> {
type Output = Self // Defaults to implementing type
@add (self, rhs: Rhs) -> Self.Output
}
trait Iterator {
type Item // No default - must be specified
@next (self) -> Option<Self.Item>
}
trait IntoIterator {
type Item
type Iter: Iterator = Self // Default to Self if Self: Iterator
@into_iter (self) -> Self.Iter
}
Semantics
- Default applies when impl omits the associated type
Selfin default position refers to the implementing type- Defaults are evaluated at impl site
- Defaults can reference type parameters and other associated types
trait Container {
type Item
type Iter = [Self.Item] // Default references another associated type
}
Self Resolution
Self in a default associated type refers to the implementing type:
trait Add<Rhs = Self> {
type Output = Self
@add (self, rhs: Rhs) -> Self.Output
}
impl Point: Add {
// Output = Self = Point (default)
@add (self, rhs: Point) -> Point = ...
}
impl Vector2: Add<int> {
type Output = Vector2 // Explicit override
@add (self, rhs: int) -> Vector2 = ...
}
Grammar Change
Current:
trait_item = associated_type | method_sig .
associated_type = "type" identifier [ ":" bounds ] .
Proposed:
trait_item = associated_type | method_sig .
associated_type = "type" identifier [ ":" bounds ] [ "=" type ] .
Override Behavior
Impls can always override the default:
trait Add<Rhs = Self> {
type Output = Self
@add (self, rhs: Rhs) -> Self.Output
}
// Use default: Output = Self = BigInt
impl BigInt: Add {
@add (self, rhs: BigInt) -> Self = ...
}
// Override: Output = bool (not Self)
impl Set: Add {
type Output = bool // Union returns whether any new elements added
@add (self, rhs: Set) -> bool = ...
}
Bounds on Defaults
Defaults must satisfy any bounds on the associated type:
trait Process {
type Output: Clone = Self // Default only valid if Self: Clone
@process (self) -> Self.Output
}
impl String: Process { // OK: String: Clone
@process (self) -> Self = self.clone()
}
impl Connection: Process { // ERROR if Connection: !Clone and no override
// Must provide explicit Output type since Self doesn't satisfy Clone
type Output = ConnectionHandle
@process (self) -> ConnectionHandle = ...
}
Semantics Details
Bounds Checking with Defaults
When an impl uses a default associated type:
- Substitute
Selfwith the implementing type - Substitute any referenced associated types
- Verify the resulting type satisfies all bounds on the associated type
If the default does not satisfy bounds after substitution, it is a compile error at the impl site. The impl must provide an explicit associated type.
Self in Trait Definition Scope
Self in a default associated type refers to the implementing type. This is resolved at the impl site, not at trait definition time. This follows the same semantics as default type parameters.
Implementation
Type Checker Changes
- Parse default type in
associated_typegrammar rule - When checking
impl Type: Trait:- Collect provided associated types
- For missing associated types with defaults:
- Substitute
Selfwith implementing type - Substitute other associated types if referenced
- Verify default satisfies bounds
- Substitute
- Proceed with normal impl checking
Example Resolution
trait Add<Rhs = Self> {
type Output = Self
@add (self, rhs: Rhs) -> Self.Output
}
impl Point: Add {
@add (self, rhs: Point) -> Point = ...
}
Resolution steps:
impl Point: Add- no associated types provided- Trait has
type Output = Selfdefault - Substitute
Self→Point - Result:
Output = Point
Interaction with Default Type Parameters
Default associated types can reference default type parameters:
trait Convert<T = Self> {
type Output = T // References type parameter
@convert (self) -> Self.Output
}
impl String: Convert {
// T = Self = String (from default type param)
// Output = T = String (from default associated type)
@convert (self) -> String = self.clone()
}
Alternatives Considered
No Defaults
Require all associated types to be specified.
Rejected: Too verbose for operator traits where Output is almost always Self.
Inference from Method Signatures
Infer associated types from method return types.
Rejected: Complex, potentially ambiguous, less explicit.
References
- Rust: Default associated types (stabilized in 1.0 for some cases)
- Swift: Associated type defaults
- Current grammar:
docs/ori_lang/v2026/spec/grammar.ebnf