trait MyMul<Rhs = Self> {
type Output;
fn mul(self, rhs: Rhs) -> Self::Output;
}
// impl 1
impl MyMul<i8> for i16 {
type Output = i16;
fn mul(self, rhs: i8) -> i16 {
self * rhs as i16
}
}
// impl 2
impl MyMul<i16> for i8 {
type Output = i32;
fn mul(self, rhs: i16) -> i32 {
self as i32 * rhs as i32
}
}
fn main() {
// let _ = dbg!(8.mul(16)); // doesn't work
// let _: i32 = dbg!(8.mul(16)); // doesn't work
let _ = dbg!(8.mul(16i16)); // works
let _ = dbg!(8i8.mul(16)); // works
// with impl 1 commented out
// let _ = dbg!(8.mul(16)); // works
// let _: i32 = dbg!(8.mul(16)); // works (apparently)
}
It seems that with both impls existing, the compiler could find the desired impl based on Self type / generic type paremeter Rhsonly, but not based on assoc type Output.
This isn't an answer as to why, but the trait parameters and implementing type are considered inputs to an implementation, whereas associated types are considered outputs (they are determined by the inputs). The compiler is attempting to choose the appropriate implementation based on the inputs.
The single implementation versions work because the compiler will just choose that implementation once it decides it's the only candidate. It's still not looking at the associated type to find the implementation. For example, this says "the types don't match", not "I couldn't find an applicable implementation".
Your examples are somewhat complicated by integers having a fall-back type: if it finds multiple possible implementations while an integer is ambiguous, it decides the integer is an i32, and then succeeds or fails based on that. (Tangential "surprising inference" topic.)