rusttype-inferencehindley-milnerconst-generics

Rust type inference failing with const generic


I am running across a failure in Rust's type inference that I can't get my head around - as most of these cases go, I don't know whether this is a compiler bug / insufficiency, or the types shouldn't be inferred and I just don't understand why. Unfortunately, a minimum working example is going to take a bit of effort.

Our fundamental goal is to implement capability that dispatches to the num package in order to do math over vectors with custom types.

We begin with three generic traits: Producer, AbsSub, and Power.

use num_traits::{Pow, Signed};

pub trait Producer<B, const LEVEL: bool> {
    /// Produce a single element
    fn produce(&self) -> B;
}

pub trait AbsSub<A, B, const LEVEL: bool> {
    // Vec isn't important here, we just need a generic return type
    fn abs_sub(&self, other: &B) -> Vec<A>
    where
        A: Signed,
        B: Producer<A, LEVEL>;
}

pub trait Power<A, B, C, const LEVEL: bool> {
    fn power(&self, other: &C) -> Vec<A::Output>
    where
        A: Pow<B> + Copy,
        C: Producer<B, LEVEL>;
}

Of course, any type can produce itself, so we'll go ahead and add a blanket implementation:

impl<A> Producer<A, false> for A // Note the `false` here
where
    A: Copy,
{
    fn produce(&self) -> A {
        *self
    }
}

Now, we'll define a wrapper type, Wrapper, that we also want to produce. If we'd defined Producer without our const bool we'd be out of luck: our blanket implementation would mean we'd implemented Producer twice for the same type. Instead, we'll implement the true version of Producer for Wrapper:

#[derive(Debug, Clone, Copy)]
struct Wrapper<A>(A);

impl<A> Producer<A, true> for Wrapper<A>
where
    A: Copy,
{
    fn produce(&self) -> A {
        self.0
    }
}

Finally, we'll add implementations of AbsSub and Power and a main:

impl<A, B, const LEVEL: bool> AbsSub<A, B, LEVEL> for Vec<A> {
    fn abs_sub(&self, other: &B) -> Vec<A>
    where
        A: Signed,
        B: Producer<A, LEVEL>,
    {
        self.iter().map(|v| v.abs_sub(&other.produce())).collect()
    }
}

impl<A, B, C, const LEVEL: bool> Power<A, B, C, LEVEL> for Vec<A>
where
    C: Producer<B, LEVEL>,
{
    fn power(&self, other: &C) -> Vec<<A>::Output>
    where
        A: Pow<B> + Copy,
    {
        self.iter().map(|v| v.pow(other.produce())).collect()
    }
}

fn main() {
    let vec = vec![1, 2, 3];

    // `AbsSub` compiles and works
    let wrapped = Wrapper { 0: 2 };
    let result = vec.abs_sub(&2);
    assert_eq!(result, vec![0, 0, 1]);
    let result = vec.abs_sub(&wrapped);
    assert_eq!(result, vec![0, 0, 1]);

    let wrapped = Wrapper { 0: 2u8 };
    let result = vec.power(&2u8); // This compiles
    let result = vec.power(&wrapped); // This doesn't
    let result = Power::<_, _, _, true>::power(&vec, &wrapped); // But this does
}

It's incredible to me that any of this works, frankly. But I don't understand why inference works for the wrapped abs_sub, but not for the wrapped power. I get a ton of compiler errors:

error[E0284]: type annotations needed
  --> src/main.rs:75:22
   |
75 |     let result = vec.power(&wrapped);
   |                      ^^^^^
   |
   = note: cannot satisfy `<i32 as Pow<_>>::Output == _`
help: try using a fully qualified path to specify the expected types
   |
75 |     let result = <Vec<i32> as Power<i32, B, Wrapper<u8>, LEVEL>>::power(&vec, &wrapped);
   |                  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++   ~
error[E0284]: type annotations needed
  --> src/main.rs:75:22
   |
75 |     let result = vec.power(&wrapped);
   |                      ^^^^^
   |
note: required by a const generic parameter in `Power::power`
  --> src/main.rs:7:26
   |
7  | pub trait Power<A, B, C, const LEVEL: bool> {
   |                          ^^^^^^^^^^^^^^^^^ required by this const generic parameter in `Power::power`
8  |     fn power(&self, other: &C) -> Vec<A::Output>
   |        ----- required by a bound in this associated function
help: try using a fully qualified path to specify the expected types
   |
75 |     let result = <Vec<i32> as Power<i32, B, Wrapper<u8>, LEVEL>>::power(&vec, &wrapped);
   |                  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++   ~
error[E0283]: type annotations needed
  --> src/main.rs:75:22
   |
75 |     let result = vec.power(&wrapped);
   |                      ^^^^^
   |
   = note: multiple `impl`s satisfying `i32: Pow<_>` found in the `num_traits` crate:
           - impl Pow<u16> for i32;
           - impl Pow<u32> for i32;
           - impl Pow<u8> for i32;
           - impl Pow<usize> for i32;
note: required by a bound in `Power::power`
  --> src/main.rs:10:12
   |
8  |     fn power(&self, other: &C) -> Vec<A::Output>
   |        ----- required by a bound in this associated function
9  |     where
10 |         A: Pow<B> + Copy,
   |            ^^^^^^ required by this bound in `Power::power`
help: try using a fully qualified path to specify the expected types
   |
75 |     let result = <Vec<i32> as Power<i32, B, Wrapper<u8>, LEVEL>>::power(&vec, &wrapped);
   |                  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++   ~
error[E0283]: type annotations needed
  --> src/main.rs:75:22
   |
75 |     let result = vec.power(&wrapped);
   |                      ^^^^^
   |
note: multiple `impl`s satisfying `Wrapper<u8>: Producer<_, _>` found
  --> src/main.rs:24:1
   |
24 | / impl<A> Producer<A, false> for A
25 | | where
26 | |     A: Copy,
   | |____________^
...
33 | / impl<A> Producer<A, true> for Wrapper<A>
34 | | where
35 | |     A: Copy,
   | |____________^
note: required by a bound in `Power::power`
  --> src/main.rs:11:12
   |
8  |     fn power(&self, other: &C) -> Vec<A::Output>
   |        ----- required by a bound in this associated function
...
11 |         C: Producer<B, LEVEL>;
   |            ^^^^^^^^^^^^^^^^^^ required by this bound in `Power::power`
help: try using a fully qualified path to specify the expected types
   |
75 |     let result = <Vec<i32> as Power<i32, B, Wrapper<u8>, LEVEL>>::power(&vec, &wrapped);
   |                  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++   ~
error[E0284]: type annotations needed
  --> src/main.rs:75:22
   |
75 |     let result = vec.power(&wrapped);
   |                      ^^^^^
   |
note: required for `Vec<i32>` to implement `Power<i32, _, Wrapper<u8>, _>`
  --> src/main.rs:42:34
   |
42 | impl<A, B, C, const LEVEL: bool> Power<A, B, C, LEVEL> for Vec<A>
   |               -----------------  ^^^^^^^^^^^^^^^^^^^^^     ^^^^^^
   |               |
   |               unsatisfied trait bound introduced here
help: try using a fully qualified path to specify the expected types
   |
75 |     let result = <Vec<i32> as Power<i32, B, Wrapper<u8>, LEVEL>>::power(&vec, &wrapped);
   |                  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++   ~

If anyone can even provide any explanation for this behavior, that would be awesome. I'm doubtful that there is a "fix", but I'd really like to know whether this is a compiler insufficiency or a true inference ambiguity.


Solution

  • Lets think like a compiler when deducing

    vec.power(&wrapped);
    

    Skipping past most of method lookup, we know we're trying to match the Power trait on Vec<i32> with Wrapper<u8> (those deductions are at least available in the error output).

    What is B? The only things to work from are the constraints:

    Unfortunately, neither constraint can deduce a singular B based on A and C. Both are "open sets": a C can implement multiple Producers and an A can implement multiple Pows.

    There definitely are multiple implementations of Pow for i32 - the error output shows this. So that is definitely not helpful.

    And then there are two implementations of Producer for Wrapper<u8>: one blanket implementation with LEVEL=false and one generic implementation on Wrapper<A> with LEVEL=true. Unfortunately for you, in the face of any ambiguity, the compiler gives up and requires you to clarify. It does not make any exploratory deductions based LEVEL to see that one doesn't work.

    Now when you specify <_, _, _, true>, that does give it enough help to rule our the blanket implementation such that only one implementation remains and thus B can be deduced.