Why not generalize primitive ops impls?

Currently, just looking at Add and u32, the macro-expanded impls are:

// core::ops::arith::add_impl!
impl Add for u32 {
    type Output = u32;
    fn add(self, other: u32) -> u32 { self + other }
}

// core::internal_macros::forward_ref_binop!
impl Add<u32> for &'_ u32 {
    type Output = <u32 as Add<u32>>::Output;
    fn add(self, other: u32) -> <u32 as Add<u32>>::Output {
        Add::add(*self, other)
    }
}
impl Add<&'_ u32> for u32 {
    type Output = <u32 as Add<u32>>::Output;
    fn add(self, other: &u32) -> <u32 as Add<u32>>::Output {
        Add::add(self, *other)
    }
}
impl Add<&'_ u32> for &'_ u32 {
    type Output = <u32 as Add<u32>>::Output;
    fn add(self, other: u32) -> <u32 as Add<u32>>::Output {
        Add::add(*self, *other)
    }
}

// core::ops::arith::add_assign_impl!
impl AddAssign for u32 {
    fn add_assign(&mut self, other: u32) { *self += other }
}

// core::internal_macros::forward_ref_op_assign!
impl AddAssign<&'_ u32> for u32 {
    fn add_assign(&mut self, other: &u32) {
        AddAssign::add_assign(self, *other);
    }
}

// core::iter::traits::accum::integer_sum_product!
impl Sum for u32 {
    fn sum<I: Iterator<Item=u32>>(iter: I) -> u32 {
        iter.fold(0, Add::add)
    }
}
impl<'a> Sum<&'a u32> for u32 {
    fn sum<I: Iterator<Item=&'a u32>>(iter: I) -> u32 {
        iter.fold(0, Add::add)
    }
}

These can be (overly?) reduced to use generics rather than simply copy-pasting the cross product of all the combinations:

// Base case
impl Add for u32 {
    type Output = u32;
    fn add(self, other: u32) -> u32 { self + other }
}

// remove right-ref
impl Add<&'_ u32> for u32 {
    type Output = u32;
    fn add(self, other: &u32) -> u32 { self + *other }
}

// remove left-ref
impl<T> Add<T> for &'_ u32
where
    u32: Add<T, Output=u32>,
{
    type Output = u32;
    fn add(self, other: T) -> u32 { *self + other }
}

// add assign
impl<A> AddAssign<A> for u32
where
    u32: Add<A, Output=u32>,
{
    fn add_assign(&mut self, rhs: A) { *self = *self + rhs }
}

// iter sum
impl<A> Sum<A> for u32
where
    u32: Add<A, Output=u32>,
{
    fn sum<I: Iterator<Item = A>>(iter: I) -> u32 {
        iter.fold(0, Add::add)
    }
}
The right-ref case can be generalized further, but this probably is too far
impl<A: Copy> Add<&'_ A> for u32
where
    u32: Add<A, Output=u32>,
{
    type Output = u32;
    fn add(self, other: &T) -> u32 { self + *other }
}

Is there a reason that core doesn't use the generic versions of AddAssign/Sum? Should crates providing "primitive-integer-like" types prefer imitating core's macro-pasted impls or the generic versions?

Wouldn't these be breaking changes? Suppose I already impl for my specific type that would collide with your generic T?

Ah, yes, this would be breaking for std to add now.

My conjecture for why std didn't go for the generalized versions in the first place is basically that the ref versions were just added to fixup behavior after the 1.0.0 path ops reform to accept the same set of types, and that's it.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.