Skip to content

Adding non-divergent core::ops overload breaks type inference and default integer type preference  #98357

Open
@mqudsi

Description

@mqudsi

This code compiles fine:

use core::ops::Mul;

#[derive(Debug)]
struct Foo(i32);

impl Mul<Foo> for i32 {
    type Output = Foo;

    fn mul(self, other: Foo) -> Self::Output {
        Foo(self * other.0)
    }
}

fn main() {
    let prod1 = 7 * Foo(6);
    assert_eq!(prod1.0, 42);
}

Adding an impl Mul<Foo> for i8 (or any other integer type) breaks the type inference happening on the first line of main() (playground link):

use core::ops::Mul;

#[derive(Debug)]
struct Foo(i32);

impl Mul<Foo> for i32 {
    type Output = Foo;

    fn mul(self, other: Foo) -> Self::Output {
        Foo(self * other.0)
    }
}

impl Mul<Foo> for i8 {
    type Output = Foo;
    
    fn mul(self, other: Foo) -> Self::Output {
        Foo(self as i32 * other.0)
    }
}

fn main() {
    let prod1 = 7 * Foo(6);
    assert_eq!(prod1.0, 42);
}

The stable (1.61.0) compiler gives a better message about what's really happening here as compared to the beta or nightly (1.62.0, 1.63.0) versions. Stable prints this:

error[[E0282]](https://doc.rust-lang.org/stable/error-index.html#E0282): type annotations needed
  --> src/main.rs:24:16
   |
23 |     let prod1 = 7 * Foo(6);
   |         ----- consider giving `prod1` a type
24 |     assert_eq!(prod1.0, 42);
   |                ^^^^^ cannot infer type
   |
   = note: type must be known at this point

error[[E0283]](https://doc.rust-lang.org/stable/error-index.html#E0283): type annotations needed
  --> src/main.rs:23:19
   |
23 |     let prod1 = 7 * Foo(6);
   |                   ^ cannot infer type for type `{integer}`
   |
note: multiple `impl`s satisfying `{integer}: Mul<Foo>` found
  --> src/main.rs:6:1
   |
6  | impl Mul<Foo> for i32 {
   | ^^^^^^^^^^^^^^^^^^^^^
...
14 | impl Mul<Foo> for i8 {
   | ^^^^^^^^^^^^^^^^^^^^

The outputs of all the Mul impl blocks are non-diverging and produce Foo for all inputs. Theoretically, specifying let prod1: Foo = ... instead of let prod1 = ... would do nothing to ameliorate the situation (since either i8 or i32 impl could satisfy the code as written, even with the additional type constraint). However, simply supplying the prod1: Foo type specifier fixes the inference and lets rust's default integer preference succeed: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bf76c0fa2edf27dc656a7bc07ecaab12

Note that this doesn't happen for the commutative equivalent (Foo * numeric rather than numeric * Foo) with the correct Mul impls in place.

rust seems to know that the output is Foo in all cases, because the following emits an error with the correct type:

fn main() {
    let mut prod1 = 7 * Foo(6);
    prod1 = ();
    //assert_eq!(prod1.0, 42);
}
error[[E0271]](https://doc.rust-lang.org/nightly/error-index.html#E0271): type mismatch resolving `<i32 as Mul<Foo>>::Output == ()`
  --> src/main.rs:39:23
   |
39 |     let mut prod1 = 7 * Foo(6);
   |                       ^ type mismatch resolving `<i32 as Mul<Foo>>::Output == ()`
   |
note: expected this to be `Foo`
  --> src/main.rs:23:19
   |
23 |     type Output = Foo;
   |                   ^^^

So there's no reason why : Foo needs to be specified explicitly.

Meta

rustc --version --verbose:

2022-06-20 5750a6aa2777382bf421

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-inferenceArea: Type inferenceC-bugCategory: This is a bug.T-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions