Rust mismatched types confusing compiler

512 views Asked by At

I've attempted use an impl to handle some strange type logic. Here is a quick reconstruction of the bug:

trait Schrodingers {}

struct AliveCat;
impl Schrodingers for Container<AliveCat> {}
struct DeadCat;
impl Schrodingers for Container<DeadCat> {}

struct Container<Cat1>
    where Container<Cat1>: Schrodingers
{
    cat: Cat1,
}

impl<Cat2> Container<Cat2>
    where Container<Cat2>: Schrodingers
{
    fn dead_cat() -> Container<DeadCat> {
        let observed_cat = DeadCat;
        Container { cat: observed_cat }
    }

    fn alive_cat() -> Container<AliveCat> {
        let observed_cat = AliveCat;
        Container { cat: observed_cat }
    }
}

fn main() {
    let dead_cat = Container::dead_cat();
    let alive_cat = Container::alive_cat();
}

Which results in the compiler errors:

error[E0308]: mismatched types
  --> src/main.rs:19:26
   |
19 |         Container { cat: observed_cat }
   |                          ^^^^^^^^^^^^ expected type parameter, found struct `DeadCat`
   |
   = note: expected type `Cat2`
   = note:    found type `DeadCat`

error[E0308]: mismatched types
  --> src/main.rs:19:9
   |
19 |         Container { cat: observed_cat }
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `DeadCat`, found type parameter
   |
   = note: expected type `Container<DeadCat>`
   = note:    found type `Container<Cat2>`

error[E0308]: mismatched types
  --> src/main.rs:24:26
   |
24 |         Container { cat: observed_cat }
   |                          ^^^^^^^^^^^^ expected type parameter, found struct `AliveCat`
   |
   = note: expected type `Cat2`
   = note:    found type `AliveCat`

error[E0308]: mismatched types
  --> src/main.rs:24:9
   |
24 |         Container { cat: observed_cat }
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `AliveCat`, found type parameter
   |
   = note: expected type `Container<AliveCat>`
   = note:    found type `Container<Cat2>`

I have been able to solve this using other methods, but why would the compiler find this confusing?

1

There are 1 answers

0
E_net4 On

The compiler was "put against the wall" so to speak.

  • The struct Container<Cat> is bound to implement Schrodingers directly in the struct declaration.
struct Container<Cat>
where Container<Cat>: Schrodingers
  • An impl block for Container<C> follows it with the same trait bound.

  • And finally, we have associated function, such as alive_cat, which exists for every Cat in Container<Cat> where Container<Cat>: Schrodingers, yet disregards the type parameter Cat, returning a Container<AliveCat>.

The compiler then appears to mistakenly infer the parameter type Cat as the specific AliveCat, even though it does not necessarily true that every Cat is always AliveCat in that impl block, leading to a weird inconsistency, and a misleading error message. As far as the compiler's capabilities are concerned, this could probably work with some tweaks to Rustc, but they might not necessarily happen.

Fortunately, there are easy development guidelines to follow that prevent this issue from ever occurring:

  • Only use trait bounds on structs when absolutely necessary for your use case. Most of the time these trait bounds can be added to the applicable impl clauses instead. Otherwise, any bounds in the struct have to be applied to all subsequent impl blocks, even if that bound is not particularly relevant to the items implemented.
  • Write constructors as associated functions returning Self (or something containing Self, such as Result<Self, _>, etc). This forces you to write an independent impl block for that constructor, with no generic type involved.
impl Container<DeadCat> {
    fn dead_cat() -> Self {
        let observed_cat = DeadCat;
        Container { cat: observed_cat }
    }
}

impl Container<AliveCat> {
    fn alive_cat() -> Self {
        let observed_cat = AliveCat;
        Container { cat: observed_cat }
    }
}

See also: