Default Type Parameters on Traits
Status: Approved Approved: 2026-01-31 Author: Claude Created: 2026-01-31 Depends On: None Enables: operator-traits-proposal.md (with default-associated-types-proposal.md) Parallel With: default-associated-types-proposal.md
Summary
Allow type parameters on traits to have default values, enabling trait Add<Rhs = Self> where Rhs defaults to Self if not specified.
Motivation
Without default type parameters, every impl must specify all type arguments:
// Today: verbose
impl Vector2: Add<Vector2> { ... }
impl Vector2: Eq<Vector2> { ... }
// Goal: concise when types match
impl Vector2: Add { ... } // Rhs defaults to Self = Vector2
impl Vector2: Eq { ... } // Rhs defaults to Self = Vector2
This is especially important for operator traits where the common case is operating on the same type.
Design
Syntax
Default type parameters use = Type after the parameter name:
trait Add<Rhs = Self> {
type Output
@add (self, rhs: Rhs) -> Self.Output
}
trait Convert<Target = Self> {
@convert (self) -> Target
}
Semantics
- Default applies when impl omits the type argument
Selfin default position refers to the implementing type- Defaults are evaluated at impl site, not trait definition site
- Parameters with defaults must appear after all parameters without defaults
trait Example<T = int, U = T> {
@method (self, t: T, u: U) -> void
}
// These are equivalent:
impl Foo: Example { ... }
impl Foo: Example<int, int> { ... }
// Partial specification:
impl Bar: Example<str> { ... } // U defaults to T = str
impl Bar: Example<str, str> { ... } // equivalent
Ordering constraint example:
// Valid: default after non-default
trait Transform<Input, Output = Input> { ... }
// Invalid: non-default after default
trait Invalid<T = int, U> { ... } // Error: non-default parameter after default
Self Resolution
Self may be used as a default value in trait type parameters. It refers to the implementing type at the impl site.
trait Add<Rhs = Self> { ... }
impl Point: Add { ... }
// Rhs = Self = Point
impl Vector2: Add { ... }
// Rhs = Self = Vector2
Grammar Change
Current:
type_param = identifier [ ":" bounds ] .
Proposed:
type_param = identifier [ ":" bounds ] [ "=" type ] .
Multiple Defaults
Later parameters can reference earlier ones:
trait Transform<Input = Self, Output = Input> {
@transform (self, input: Input) -> Output
}
impl Parser: Transform { ... }
// Input = Self = Parser, Output = Input = Parser
impl Parser: Transform<str> { ... }
// Input = str, Output = Input = str
impl Parser: Transform<str, Ast> { ... }
// Input = str, Output = Ast
Implementation
Type Checker Changes
- Parse default type in
type_paramgrammar rule - When checking
impl Type: Trait:- Count provided type arguments
- Fill missing arguments with defaults
- Substitute
Selfwith implementing type - Proceed with normal impl checking
Example Resolution
trait Add<Rhs = Self> {
@add (self, rhs: Rhs) -> Self
}
impl Point: Add {
@add (self, rhs: Point) -> Self = ...
}
Resolution steps:
impl Point: Add- no type args provided- Trait has 1 type param with default
Self - Substitute
Self→Point - Result:
impl Point: Add<Point>
Alternatives Considered
No Defaults
Require all type parameters to be specified.
Rejected: Too verbose for common cases like impl MyType: Add.
Inference Instead of Defaults
Infer missing type parameters from usage.
Rejected: Less predictable, harder to understand, may conflict with other inference.
References
- Rust:
trait Add<Rhs = Self>instd::ops - Scala: Default type parameters
- Grammar: See
grammar.ebnf§ Generics