I'm trying to implement a generic function in Rust where the only requirement for the argument is that the multiplication operation should be defined. I'm trying to implement a generic "power", but will go with a simpler cube
function to illustrate the problem:
use std::ops::Mul;
fn cube<T: Mul>(x: T) -> T {
x * x * x
}
fn main() {
println!("5^3 = {}", cube(5));
}
When compiling I get this error:
error[E0369]: binary operation `*` cannot be applied to type `<T as std::ops::Mul>::Output`
--> src/main.rs:4:5
|
4 | x * x * x
| ^^^^^^^^^
|
= note: an implementation of `std::ops::Mul` might be missing for `<T as std::ops::Mul>::Output`
What does this mean? Did I choose the wrong trait? How can I resolve this?
Let's break down your example a bit:
What are the types of
a
andb
? In this case, the type ofa
is<T as std::ops::Mul>::Output
— sound familiar from the error message? Then, we are trying to multiply that type byx
again, but there's no guarantee thatOutput
is able to be multiplied by anything!Let's do the simplest thing and say that
T * T
needs to result in aT
:Unfortunately, this gives two similar errors:
Which is because the
Mul
trait takes arguments by value, so we add theCopy
so we can duplicate the values.I also switched to the
where
clause as I like it better and it is unwieldy to have that much inline:The bound
T: Mul
does not imply that the result of the binary operator is also of typeT
. The result type is an associated type of this trait:Output
.The other issue is that at some point in time the operator traits switched from pass-by-referece to pass-by-value. In generic code this can be a bit of a pain in the butt (for now at least) because these operators consume their operands unless you also require the types to be
Copy
.Just for completeness (in case you don't like to require
Copy
), let me add some information about a possible alternative direction.For the sake of generic code, authors of "numeric types" are encouraged to provide additional non-consuming implementations of these operator traits so that you don't need
Copy
orClone
. For example, the standard library already provides the following implementations:with each implementation having
f64
asOutput
type. But making use of these traits directly is not pretty:Eventually, we might get some (slightly) higher level traits, which would reduce the noise. For example:
T: Mul2
could implyT: Mul<T> + Mul<&T>
and&T: Mul<T> + Mul<&T>
. But at the time of writing this, the Rust compiler does not seem able to handle this. At least I could not successfully compile the following code:I think it's safe to say that things will improve in this area. For now, the ability to generically implement numeric algorithms in Rust is not as good as I would like it to be.