Why can't the compiler infer the appropriate implementation to use in this example?

This example:

struct Struct<T> {
    data: T,
}

impl Struct<u8> {
    fn new(data: u8) -> Struct<u8> {
        Self { data }
    }
}

impl Struct<f64> {
    fn new(data: f64) -> Struct<f64> {
        Self { data }
    }
}

fn main() {
    let s: Struct<u8> = Struct::new(0_u8);
    let s: Struct<f64> = Struct::new(0_f64);
}

playground

Throws this compile error:

error[E0034]: multiple applicable items in scope
  --> src/main.rs:18:33
   |
18 |     let s: Struct<u8> = Struct::new(0_u8);
   |                                 ^^^ multiple `new` found
   |
note: candidate #1 is defined in an impl for the type `Struct<u8>`
  --> src/main.rs:6:5
   |
6  |     fn new(data: u8) -> Struct<u8> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl for the type `Struct<f64>`
  --> src/main.rs:12:5
   |
12 |     fn new(data: f64) -> Struct<f64> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0034]: multiple applicable items in scope
  --> src/main.rs:19:34
   |
19 |     let s: Struct<f64> = Struct::new(0_f64);
   |                                  ^^^ multiple `new` found
   |
note: candidate #1 is defined in an impl for the type `Struct<u8>`
  --> src/main.rs:6:5
   |
6  |     fn new(data: u8) -> Struct<u8> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl for the type `Struct<f64>`
  --> src/main.rs:12:5
   |
12 |     fn new(data: f64) -> Struct<f64> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Which doesn't make sense to me because the argument type and the return type are explicitly annotated and in both cases it's clear only one of the two candidate functions satisfy the type requirements so why can't the Rust compiler figure it out?

My guess is that it's because this is essentially "adhoc" overloading, rather than generics or trait-based overloading. Rust probably just doesn't have the codepaths to solve that.

Can you say more about your actual use? In the trivial example you can just make a generic new -- even if there are other non-generic things too -- but I assume it's not that easy in whatever you're really doing.

Someone (else) posted nearly the same question on SO earlier; perhaps @pretzelhammer saw it and became curious enough to re-post here. (The original was deleted for unknown reasons)

So I'll write here what I was going to comment there instead.

I guess the SO OP was familiar with C++ templates, which (the following is a gross exaggeration) kind of work on the principle of "list all the candidate templates and see if any of them work". This is where we get SFINAE and ridiculously long error messages. Rust generics, however, do not work like this: the Rust compiler has to deduce the types of every expression from "first principles", in a sense. This is usually good because it means the compiler is able to fail fast and give high quality error messages when code is broken or ambiguous.

So when it sees Struct::new the compiler won't say, "hmm, that could be Struct<u8>::new or Struct<f64>::new, let's keep looking and see which one fits." It looks for a unique (before monomorphization) new function among Struct's impls, and can't find one, so it gives up.

The simplest way to solve it is, of course, to only have one new function:

trait Number {}

impl<T: Number> Struct<T> {
    fn new(data: T) -> Self {
        Self { data }
    }
}

impl Number for u8 {}
impl Number for f64 {}

This is one aspect of what people mean when they say Rust's generics are principled. The original code had u8 and f64 being "special in the same way"; they shared a property not held by any other type, but that property itself wasn't visible to the type system. The Number trait gives that property a name and makes it available for use in constraints.

4 Likes