Default Generic Type Parameters
A default generic type parameter specifies a fallback type (<T = DefaultType>) in a trait, struct, enum, or type alias declaration so users can omit the type argument in the common case while retaining the ability to override it.
Default Type Parameters on Traits (<Rhs = Self>)
Binary operator and comparison traits in the standard library (Add<Rhs = Self>, Sub<Rhs = Self>, PartialEq<Rhs = Self>) set their right-hand side type parameter to Self by default:
pub trait Add<Rhs = Self> {
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}
- Writing
impl Add for Pointuses the defaultRhs = Point(adding twoPointvalues). - Writing
impl Add<i32> for PointoverridesRhstoi32(adding a scalar offset to aPoint).
trait Combine<Rhs = Self> {
type Output;
fn combine(self, rhs: Rhs) -> Self::Output;
}
#[derive(Debug, Clone, Copy)]
struct Vec2 {
x: f64,
y: f64,
}
// 1. Uses the default parameter `Rhs = Self` (Vec2 + Vec2):
impl Combine for Vec2 {
type Output = Vec2;
fn combine(self, rhs: Vec2) -> Vec2 {
Vec2 {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
// 2. Overrides `Rhs` with `f64` (Vec2 + f64 uniform scale):
impl Combine<f64> for Vec2 {
type Output = Vec2;
fn combine(self, scale: f64) -> Vec2 {
Vec2 {
x: self.x * scale,
y: self.y * scale,
}
}
}
fn main() {
let a = Vec2 { x: 2.0, y: 3.0 };
let b = Vec2 { x: 4.0, y: 1.0 };
println!("Combine Vec2: {:?}", a.combine(b));
println!("Combine f64: {:?}", a.combine(10.0));
}
Combine Vec2: Vec2 { x: 6.0, y: 4.0 }
Combine f64: Vec2 { x: 20.0, y: 30.0 }
Default Type Parameters on Structs and Collections
Standard library collections use default type parameters for hashers and allocators so callers can write HashMap<K, V> instead of spelling out RandomState every time:
// Standard library signature:
pub struct HashMap<K, V, S = RandomState> { /* ... */ }
You can use the same technique in custom containers or builders:
struct PlainFormatter;
struct UppercaseFormatter;
struct Logger<F = PlainFormatter> {
prefix: String,
formatter: F,
}
impl Logger<PlainFormatter> {
fn new(prefix: &str) -> Self {
Self {
prefix: prefix.to_owned(),
formatter: PlainFormatter,
}
}
}
impl<F> Logger<F> {
fn with_formatter<NewF>(self, formatter: NewF) -> Logger<NewF> {
let _ = &self.formatter;
Logger {
prefix: self.prefix,
formatter,
}
}
}
fn main() {
// Type annotation `Logger` uses `Logger<PlainFormatter>` by default:
let default_log: Logger = Logger::new("APP");
let custom_log: Logger<UppercaseFormatter> = default_log.with_formatter(UppercaseFormatter);
let _ = &custom_log.formatter;
println!("Configured logger with prefix: {}", custom_log.prefix);
}
Configured logger with prefix: APP
Ordering Rule for Default Type Parameters
Type parameters with default values must appear after all type parameters without default values (struct Foo<A, B = i32> is valid; struct Foo<A = i32, B> is a syntax error).