Reputation: 43
I want to implement a trait for a generic-type struct. A method inside the trait block must return a non-generic type. I'm having problems while trying to cast:
struct Rectangle<T> {
x: T,
y: T,
width: T,
height: T,
}
trait Area {
fn area(&self) -> f64;
}
impl<T> Area for Rectangle<T>
where T: std::ops::Mul<Output=T>
{
fn area(&self) -> f64 {
let t_area = self.height * self.width;
let f_area = t_area as f64;
f_area
}
}
fn main() {
let sq = Rectangle { x: 0, y: 0, height: 1, width: 1 };
println!("{}", sq.area());
}
And the compiler output is:
error: non-scalar cast: `T` as `f64`
--> src/main.rs:22:22
|
22 | let f_area = t_area as f64;
| ^^^^^^^^^^^^^
Can I cast T
to f64
without using unsafe blocks?
Upvotes: 3
Views: 2206
Reputation: 300409
Can I, not using unsafe blocks, cast
T
tof64
?
What's T
? The code to convert u8
to f64
is certainly not the same as the code to convert u64
to f64
(the latter can fail, after all).
As I see it, you have two paths ahead of you:
Area
generic over T
, and thus return a T
T
to types that can be converted into a f64
I'll demonstrate the latter:
impl<T> Area for Rectangle<T>
where T: std::ops::Mul<Output=T> + Clone + std::convert::Into<f64>
{
fn area(&self) -> f64 {
self.height.clone().into() * self.width.clone().into()
}
}
Upvotes: 7
Reputation: 89016
Can I, not using unsafe blocks, cast
T
tof64
?
No, and there are many good reasons for that. T
could be any type, so it doesn't need to be compatible with or similar to f64
at all. The only thing you know about your T
is, that it implements std::ops::Mul<Output=T>
. But that doesn't help, because that trait doesn't say anything about f64
either.
So what you could do, is to bound T
to be std::ops::Mul<Output=f64>
. This means that multiplying two T
s will result in an f64
. Your function then works (even without the as f64
cast), but you make your impl way less generic.
The proper way to solve this problem, is to abstract over multiple kinds of numbers. Thankfully, the num-traits
crate has already done that. In particular, you are probably interested in the ToPrimitive
trait. Your impl should then probably look somewhat like:
use num_traits::ToPrimitive;
impl<T> Area for Rectangle<T>
where T: std::ops::Mul
<T as std::ops::Mul>::Output: ToPrimitive
{
fn area(&self) -> f64 {
let t_area = self.height * self.width;
t_area.to_f64().unwrap()
}
}
Minor problem: we have this unwrap()
here which panics when the multiplication result can't be converted into an f64
. I'm not quite sure why this can happen, but we need to be aware of that. Maybe there is a better solution without such an unwrap()
.
You can of course also create your own trait. Something like:
trait AsF64 {
fn cast(self) -> f64;
}
impl AsF64 for i32 {
fn cast(self) -> f64 { self as f64 }
}
// more impls for u32, u16, i16, ...
And then impl for <T as std::ops::Mul>::Output: AsF64
.
Upvotes: 3