henrikl
henrikl

Reputation: 561

Generically multiply values of different type in Rust

Say I want to write a generic function that takes some value of type K and commutatively multiply it with an f64. The following works:

fn generic1<K>(a: K, b: f64) -> K
where
    K: Mul<f64, Output = K> + Add<K, Output = K> + Copy,
    f64: Mul<K, Output = K>,
{
     a * b + b * a
}

However, I am now no longer able to e.g. multiply two floats within the same function. Effectively, it seems like the bound f64: Mul<K, Output = K> is overriding/hiding (?) the impl impl Mul<f64> for f64. Here is an example:

fn generic2<K>(a: K, b: f64) -> K
where
    K: Mul<f64, Output = K> + Add<K, Output = K> + Add<f64, Output = K> + Copy,
    f64: Mul<K, Output = K>,
{
     a * b + b * a + b * b
}

which gives the following compiler error:

   |
16 |      a * b + b * a + b * b
   |                          ^ expected type parameter, found f64
   |
   = note: expected type `K`
              found type `f64`

(playground link), i.e. f64 * <whatever> now only works for <whatever> being of type K.

As a workaround, I can use the fully qualified syntax <f64 as Mul<f64>>::mul(b, b), but that is extremely ugly. It feels like the compiler should be able to see that it can use an output of type f64 and hence use the implementation of f64 * f64 instead of f64 * K.

A less abstract example for what I want to do: K could be a vector type, or a scalar type, and I want to be able to do commutative scalar multiplication with it in a generic way.

What's going on here? Why can I no longer multiply b * b?

Update: @Ömer-erden noticed that it works when explicitly specifying f64: Mul<f64, Output = f64> as well. Unfortunately that workaround breaks when importing the alga crate - see the updated playground link.

I'm not exactly sure what alga does to break the already weird behavior. As Ömer already noted, it looks like this is more of a bug or limitation of the type checker rather than a bug in alga/whatever, since ideally no code should be able to break resolution of impl Mul<f64, Output = f64> or similar for any builtin type.

Upvotes: 5

Views: 2178

Answers (1)

&#214;mer Erden
&#214;mer Erden

Reputation: 8833

This is a constraint which tells that when K is a right operand, you can multiply f64 with K, and the output will be K. Also tells f64 must implement Mul<K, Output = K>

f64: Mul<K, Output = K>,

I don't know yet, this might happen because of limited power of type-checker or bug but somehow implementation of Mul<f64, Output = f64> on f64 becomes ambiguous because of the constraint Mul<K, Output = K>.

if you explicitly state the expected behavior which is Mul<f64, Output = f64> in your case :

fn generic2<K>(a: K, b: f64) -> K
where
    K: Mul<f64, Output = K> + Add<K, Output = K> + Add<f64, Output = K> + Copy,
    f64: Mul<K, Output = K> + Mul<f64, Output = f64>,
{
    a * b + b * a + b * b
}

it will work as expected.

Playground

Upvotes: 3

Related Questions