std::ops::Add, why Rhs is generic while Output is associated type

Update: Reason is explained in RFC.
0439-cmp-ops-reform - The Rust RFC Book (rust-lang.github.io)

Why not both generic type or both associated type?

pub trait Add<Rhs = Self> {
    type Output;
    pub fn add(self, rhs: Rhs) -> Self::Output;
}

both generic type

pub trait Add<Rhs = Self, Output=Self> {
    pub fn add(self, rhs: Rhs) -> Output;
}

both associated type

pub trait Add {
    type Rhs;
    type Output;
    pub fn add(self, rhs: Self::Rhs) -> Self::Output;
}
1 Like

If both were associated types, it would be impossible to implement Add more than once for the same type. Making Output a type parameter wouldn't gain you anything, and it would also not really model it correctly.

You can think of associated types as "outputs" of a trait, while type parameters are "inputs", they match the operands of + (inputs) and its result (output).

4 Likes

In fact, that's how the Add trait used to be, way back in pre-1.0 days before associated types existed. You can read the RFC -- which uses Add as its example -- for more about that https://rust-lang.github.io/rfcs/0195-associated-items.html#clearer-trait-matching.

4 Likes

Well, you could have something like

// impl Add<u32, u32> for u32
let i: u32 = 1_u32 + 2_u32;
// impl Add<u32, f64> for u32
let f: f64 = 1_u32 + 2_u32;

But then you couldn't do

let n /* no type given */ = 1_u32 + 2_u32;

As it is ambiguous; your additions would need annotated in a lot more places. Even when able to be inferred, it would be confusing if + acted this way.

Playground.

7 Likes