I have a newbie question about generics in Rust (version 1.0).
Let's say I write a generic function to do division. Never mind the usefulness of such a function; it's a simple function to keep this question simple.
fn divide<T: std::ops::Div>(a: T, b: T) -> T {
a / b
}
fn main() {
println!("{}", divide(42, 18))
}
This program fails to compile.
src/main.rs:2:5: 2:10 error: mismatched types:
expected `T`,
found `<T as core::ops::Div>::Output`
(expected type parameter,
found associated type) [E0308]
src/main.rs:2 a / b
^~~~~
I understand that the compiler error is telling me that the result of the division operation is type Output
, not T
, and I see the Output
type in the standard library documentation.
How do I convert from Output
to T
? I try to use as
to cast.
fn divide<T: std::ops::Div>(a: T, b: T) -> T {
(a / b) as T
}
fn main() {
println!("{}", divide(42, 18))
}
This causes a different compiler error.
src/main.rs:2:5: 2:17 error: non-scalar cast: `<T as core::ops::Div>::Output` as `T`
src/main.rs:2 (a / b) as T
^~~~~~~~~~~~
I'm out of ideas to make this work, and I realize I lack understanding of something fundamental about the language here, but I don't even know what to look for to make this work. Help?
You simply have to specify T::Output
as the return type of the function:
fn divide<T: std::ops::Div>(a: T, b: T) -> T::Output {
a / b
}
Edit to add more explanation on why you cannot do the cast inside the function
When you are IN your generic function divide, the compiler yet doesn't know that you can cast T
to T::Output
, so the cast is invalid. They are generic types, they can be anything, how the compiler knows that you can cast from T
to T::Output
?
a / b
produces something of type T::Output
, so in the solution above there is not cast, T::Output
is simply the right type.
Edit to add another possible solution using std::convert::From
The most (I think) generic implementation is when you know that the cast from T::Output
to T is possible. You can bound T
to implement From
for T::Output
.
This is a complete example:
use std::ops::Div;
use std::convert::From;
fn divide<T: Div>(a: T, b: T) -> T
where T: From<<T as Div>::Output>
{
T::from(a / b)
}
#[derive(Debug)]
struct Bip(u32);
impl Div for Bip {
type Output = f32;
fn div(self, rhs: Bip) -> f32 {
(self.0 / rhs.0) as f32
}
}
impl From<f32> for Bip {
fn from(value: f32) -> Self {
Bip(value as u32)
}
}
fn main() {
println!("{:?}", divide(12, 4));
println!("{:?}", divide(Bip(12), Bip(4)));
}
Andreas' answer explains why the cast is impossible, and that making the function as generic as possible solves this issue.
It is not, however, the only solution. Rust also supports the ability to constrain the associated types (Output
here) of a generic function.
An alternative would be:
use std::ops::Div;
fn divide<T>(a: T, b: T) -> T
where T: Div<Output = T>
{
a / b
}
The <Output = T>
bit instructs the compiler to only accept in this functions types T
for which the implementation of Div
has an Output
equal to T
. This is obviously more restricting, but it allows ensuring that the result of divide
is of type T
.