I am trying to understand generic functions. I know this topic has been discussed extensively, but I can't seem to wrap my head around some details.
I am attempting something similar to this, but even simpler. I just want a double
function that multiplies the argument by two and I want it to work for any float or integer type. So far I have this:
use std::convert::From;
use std::ops::Mul;
fn double<A>(x: A) -> A
where A: Mul<Output = A> From<f32> {
return x * A::from(2f32);
}
This works fine, when I call it with a float (f32
or f64
):
println!("{:?}", double(2f64));
This gives me 4.0
.
But it fails for any integer type, for example:
printl!("{:?}", double(2i32));
This gives the following error:
println!("{:?}", double(2i32));
------ ^^^^ the trait `From<f32>` is not implemented for `i32`
I understand the error. There is no general way to cast a float to an integer. It's ambiguous how that is supposed to work, if I wanted to cast 2.5
inside my function instead of 2.0
.
I know there is the TryFrom
trait, but I cannot figure out, how to make use of it in this situation.
I tried it the other way around, i.e. requiring A
to implement From<u32>
for example, but this just switches the error from occurring with integers to occur with floats. But what surprised me is that it also leads to an error when I call the function with a signed integer like 2i32
.
What am I missing? How can I realize a generic double
function over any number type?
CodePudding user response:
Since you just need to convert the number 2
to whatever type, why not use From<u8>
?
use std::ops::Mul;
fn double<A>(x: A) -> A
where
A: Mul<Output = A> From<u8>,
{
return x * A::from(2);
}
fn main() {
println!("{}", double(1));
println!("{}", double(1f32));
println!("{}", double(1f64));
}
The only downside of this simple approach is that double(1i8)
will fail to compile (but it will work with i16
, i32
, etc.). This is because From<u8>
is not implemented for i8
because there are some u8
values that cannot be represented as i8
. However, we know we really don't need to represent all u8
values as A
, but just 2u8. That's why we want to use TryFrom
:
fn double<A>(x: A) -> A
where
A: Mul<Output = A> TryFrom<u8>,
{
x * A::try_from(2).unwrap_or_else(|_| unreachable!())
}
The unwrap will never panic for numeric types because there is no numeric type that cannot represent the number 2.
CodePudding user response:
From<f32>
means that any f32
value can be converted to the target type without loss. This obviously can't be done for i32
since all non-integer values would lose their fractional part in the conversion.
Conversely, From<u32>
can't be done for f32
because f32
only have 24 bits of mantissa, so any value greater than 2^24
would loose precision in the conversion. From<u32>
also can't be done for i32
since values greater than 2^31
can't be represented.
Since the only value you're interested in converting is 2
, you can use From<i8>
or From<u8>
, but note that it will still fail for the other one:
use std::ops::Mul;
fn double<A>(x: A) -> A
where A: Mul<Output = A> From<i8> {
return x * A::from(2);
}
fn main() {
println!("{}", double(1));
println!("{}", double(1f32));
println!("{}", double(1f64));
// println!("{}", double(1u8)); // Doesn't work
}