I want to have a Rectangle
type that is generic over its dimensions which implements an Area
trait. The only requirement on the output of this trait is for it to be Display
able. The requirement for generic dimensions is contrived, but I'm treating it as a learning example. The following compiles just fine:
use std::fmt;
use std::ops;
trait Area{
type Output: fmt::Display;
fn area(&self) -> Self::Output;
}
struct Rectangle <T, U>{
width: T,
height: U,
}
impl <T, U> Area for Rectangle<T, U>
where T: ops::Mul<U> + Copy, U: Copy,
<T as ops::Mul<U>>::Output: fmt::Display
{
type Output = <T as ops::Mul<U>>::Output;
fn area(&self) -> Self::Output {
self.width * self.height
}
}
fn main(){
let float_r = Rectangle {width: 1.5_f64, height: 2.5_f64};
let int_r = Rectangle {width: 2_i32, height: 3_i32};
println!("Area of rectangle is {}", float_r.area());
println!("Area of rectangle is {}", int_r.area());
}
I now want to refactor the logging lines into a function that accepts any type which implements Area
to later user with e.g a Circle
type:
fn log_shape(s: &dyn Area){
println!("Area of rectangle is {}", s.area());
}
but this does not compile unless I define the Output
in the function signature:
error[E0191]: the value of the associated type `Output` (from trait `Area`) must be specified
--> src/main.rs:24:22
|
5 | type Output: fmt::Display;
| -------------------------- `Output` defined here
...
24 | fn log_shape(s: &dyn Area){
| ^^^^ help: specify the associated type: `Area<Output = Type>`
For more information about this error, try `rustc --explain E0191`.
however, I don't want to do this, since if I define a circle:
struct Circle<T>{
radius: T
}
the Output
for Circle
's implementation of Area
is of type <<T as ops::Mul<T>>::Output as ops::Mul<f64>>::Output
(corresponding to self.radius * self.radius * pi
): which is a different type to that of Rectangle
's area.
Is my intended use of associated types in this context misguided? If so, what is the advised way of achieving my desired functionality?
The issue is that when you call s.area()
, the return type needs to have a sized, known type, which is why the error wants you to specify it.
A few ways around this (mostly from the comments):
Here I made a new trait but you may want to replace your existing trait (playground).
impl<T, U> Area for Rectangle<T, U>
where
T: ops::Mul<U> + Copy,
U: Copy,
{
type Output = <T as ops::Mul<U>>::Output;
fn area(&self) -> Self::Output {
self.width * self.height
}
}
trait Log {
fn log(&self);
}
impl<T> Log for T
where
T: Area,
T::Output: Display,
{
fn log(&self) {
println!("Area of rectangle is {}", self.area());
}
}
fn log_shape(s: &dyn Log) {
s.log()
}
This is more idiomatic and likely faster if your use case allows (playground).
fn log_shape<A: Area>(s: A) {
println!("Area of rectangle is {}", s.area());
}
This is likely slower and has a different flavor, yet similar level, of flexibility to the first method (playground).
trait Area {
fn area(&self) -> Box<dyn Display>;
}
impl<T, U> Area for Rectangle<T, U>
where
T: ops::Mul<U> + Copy,
U: Copy,
<T as ops::Mul<U>>::Output: Display + 'static,
{
fn area(&self) -> Box<dyn Display> {
let a = self.width * self.height;
Box::new(a)
}
}
In summary: #1 works with trait objects while being performant (no extra allocations). If you have it take a Formatter
parameter like in Display::fmt
, you can use it to write to any writer. #2 is the most idiomatic and fastest if you can use it. #3 lets you leave the log_shape
function unchanged.