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:
fn cube<T: Mul>(x: T) -> T {
let a = x * x;
let b = a * x;
b
}
What are the types of a
and b
? In this case, the type of a
is <T as std::ops::Mul>::Output
— sound familiar from the error message? Then, we are trying to multiply that type by x
again, but there\'s no guarantee that Output
is able to be multiplied by anything!
Let\'s do the simplest thing and say that T * T
needs to result in a T
:
fn cube<T: Mul<Output = T>>(x: T) -> T {
x * x * x
}
Unfortunately, this gives two similar errors:
error[E0382]: use of moved value: `x`
--> src/lib.rs:6:9
|
6 | x * x * x
| - ^ value used here after move
| |
| value moved here
|
= note: move occurs because `x` has type `T`, which does not implement the `Copy` trait
Which is because the Mul
trait takes arguments by value, so we add the Copy
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:
fn cube<T>(x: T) -> T
where
T: Mul<Output = T> + Copy
{
x * x * x
}
The bound T: Mul
does not imply that the result of the binary operator is also of type T
. 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
or Clone
. For example, the standard library already provides the following implementations:
f64 implements Mul< f64>
f64 implements Mul<&f64>
&f64 implements Mul< f64>
&f64 implements Mul<&f64>
with each implementation having f64
as Output
type. But making use of these traits directly is not pretty:
fn cube<T>(x: &T) -> T
where for<\'a> T: Mul<&\'a T, Output = T>,
for<\'a,\'b> &\'a T: Mul<&\'b T, Output = T>
{
x * x * x
}
Eventually, we might get some (slightly) higher level traits, which would reduce the noise. For example: T: Mul2
could imply T: 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:
use std::ops::Mul;
pub trait Mul2 where
Self: Mul<Self, Output=Self>,
Self: for<\'a> Mul<&\'a Self, Output=Self>,
for<\'a> &\'a Self: Mul<Self, Output=Self>,
for<\'a,\'b> &\'a Self: Mul<&\'b Self, Output=Self> {}
impl<T> Mul2 for T where
T: Mul<T, Output=T>,
T: for<\'a> Mul<&\'a T, Output=T>,
for<\'a> &\'a T: Mul<T, Output=T>,
for<\'a,\'b> &\'a T: Mul<&\'b T, Output=T> {}
fn cube<T: Mul2>(x: &T) -> T {
x * x * x
}
fn main() {
let c = cube(&2.3);
println!(\"Hello, world! {}\", c)
}
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.