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 can cast one instance of a generic class into another only if the two are otherwise compatible and their type arguments are the same.
A generic type is a generic class or interface that is parameterized over types. The following Box class will be modified to demonstrate the concept.
Implementing generics into your code can greatly improve its overall quality by preventing unprecedented runtime errors involving data types and typecasting.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With