I've been reading through the Rust book online and I've reached 4.1, Operators and Overloading. I noticed that std::ops::Add
defines it as fn add(self, rhs: RHS) -> Self::Output;
with a type Output
defined separately in the trait.
I understand what's going on here: the add
function accepts the left hand side as self
, and the right hand side as the generic parameter type RHS
.
I want to know why is the output type defined with the Output
alias, instead of being another generic (ex. Add<RHS, Output>
)? Is it just a convention, or is there a specific reason to it?
While functions operate on variables, you can think of traits as functions that operate on types. When thought of this way, the type parameters serve as inputs to the function and associated types serve as outputs.
Since Output
is an associated type, for two types A
and B
that we wish to impl Add
, we are restricted to picking a single Output
type. Were Output
a type parameter, we could impl Add
for A
and B
in a myriad of ways.
For example, let us define a Mul
trait where Output
is a parameter:
trait Mul<RHS, Output> {
fn mul(self, rhs: RHS) -> Output;
}
Now let us define a Complex
type:
#[derive(Debug, Clone, Copy)]
struct Complex {
x: f64,
y: f64,
}
impl Complex {
fn new(x: f64, y: f64) -> Complex {
Complex { x: x, y: y }
}
}
We want to be able to multiply it by f64
:
impl Mul<f64, Complex> for Complex {
fn mul(self, rhs: f64) -> Complex {
Complex::new(self.x * rhs, self.y * rhs)
}
}
This all works fine. However, we could come up with a second implementation:
impl Mul<f64, f64> for Complex {
fn mul(self, rhs: f64) -> f64 {
self.x * rhs
}
}
When we multiply a Complex
by a f64
now, it is ambiguous which implementation should be called, and extra type information is needed to be supplied by the caller. By making Output
an associated type, this is disallowed. The following code throws a compiler error for conflicting implementations:
impl std::ops::Mul<f64> for Complex {
type Output = Complex;
fn mul(self, rhs: f64) -> Complex {
Complex::new(self.x * rhs, self.y * rhs)
}
}
impl std::ops::Mul<f64> for Complex {
type Output = f64;
fn mul(self, rhs: f64) -> Complex {
self.x * rhs
}
}
Full example
It's about who gets to specify the output type. Generally when you multiply two types, say f64
by f64
, it's clear what the type of the result should be: f64
. It wouldn't make sense for the caller to ask for a result type of String
- so it doesn't really make sense as an input.
I can imagine some cases where you might want the choice different output, but they're a bit niche (say u32
* u32
returning the full u64
result?); plus if there were more than one option, how do you specify which one you want? While you could write Add<u32, u64>::add(a, b)
, it would make simple expressions like a*b*c
ambiguous and make type inference much harder!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With