Lend me your google-fu on the issue tracker


#1

Today, I have run into an error most magical.

It turns out that if you write f64: Mul<C> as a trait bound on a function, then rustc forgets that f64s can be multiplied with f64s.

use ::std::ops::Mul;

fn foo<C>(max: C) -> <f64 as Mul<C>>::Output
where f64: Mul<C>,
{
    1.0f64 * 3.0f64 * max
}

fn main() { }
   Compiling wtfbounds v0.1.0 (file:///home/lampam/cpp/throwaway/wtfbounds)
error[E0308]: mismatched types
 --> src/main.rs:6:14
  |
6 |     1.0f64 * 3.0f64 * max
  |              ^^^^^^ expected type parameter, found f64
  |
  = note: expected type `C`
             found type `f64`

error[E0369]: binary operation `*` cannot be applied to type `<f64 as std::ops::Mul<C>>::Output`
 --> src/main.rs:6:5
  |
6 |     1.0f64 * 3.0f64 * max
  |     ^^^^^^^^^^^^^^^^^^^^^
  |
  = note: an implementation of `std::ops::Mul` might be missing for `<f64 as std::ops::Mul<C>>::Output`

error: aborting due to 2 previous errors

error: Could not compile `wtfbounds`.

To work around this, it is insufficient even to add a f64: Mul<f64, Output=f64> bound; you must write out the full UFCS <f64 as Mul<f64>>::mul(1.0, 3.0). It has behaved like this since rust 1.0.

Anyways, since there’s no way on Earth that an issue like this has never been discovered and reported before, I’ve been idly searching the issue tracker with various vague keywords like “bound,” but haven’t found anything that sounds like it could be related, yet.

…it’s gotta be there, right?


Edit: I “minimized” my example by adding f64 type suffixes, to show that the issue does not have to do with untyped-float-literal magicks


Edit: It is not specific to binops (the traits or the sugar)…
Edit: …nor do the type arguments to the trait even need to be capable of unifying. All you need is an impl with a generic type argument to the trait, after which all monomorphic impls are forgotten (and outright ignored if you try to explicitly specify them)

use ::std::ops::Mul;

trait Trait<A>: Sized {
    fn lel(self, a: A) -> Self { self}
}

struct Foo;
struct Bar<C>(C);
impl Trait<Foo> for f64 {}
impl<C> Trait<Bar<C>> for f64 {}

fn foo<C>() -> f64
where
    f64: Trait<Foo>,      // <- rustc will completely forget about this...
    f64: Trait<Bar<C>>,   // <- ...so long as this is generic
{
    (1.0f64).lel(Foo).lel({ let b: Bar<C> = panic!(); b })
}

fn main() { }

Why does a where clause confuse lifetime analysis?
#2

Require Mul<Output=C>. Rust allows operators to produce any other type, different from the operands.


#3

That’s entirely unrelated. I shall make the test case even more obvious.

use ::std::ops::Mul;

fn multiply_two_floats<C>()
where f64: Mul<C>,
{
    // This produces a type error on the second operand.
    let _ = 1.0_f64 * 1.0_f64;
}

#4

Given that you haven’t had much luck finding an existing issue, you should just go ahead and create a report anyway.

I simplified your test case a bit further. It appears unrelated to f64 nor self, nor is it affected by the way the where clause is written. Seems like an inference problem: the compiler appears to eagerly to commit to certain implementations over others. You can work around this problem by writing <Foo as Trait<Foo>>::lel(&Foo) explicitly, but it’s ugly.

struct Foo;
struct Bar<C>(C);

trait Trait<A> { fn lel(a: &A) { } }
impl Trait<Foo> for Foo {}
impl<C> Trait<Bar<C>> for Foo {}

fn foo<C>() where Foo: Trait<Bar<C>> + Trait<Foo> {
    Foo::lel(&Foo);
}

fn main() { }

#5

Can anyone link me to the issue for this, if one has been created? If one hasn’t been created, I would be happy to create one myself.


#6