I am trying a struct with a trait interface that I can change.
as in two structs Circle
and Square
that both implement the trait interface Shape
Then have a Data
struct that holds a Shape
that I can easily switch out for one or the other.
This I found achievable with dyn Shape
, but I have a library function (that I can not change) that looks something like this:
fn lib_display_area<P>(p: P)
where
P: Shape,
{
println!("area is {}", p.area())
}
This does not like taking in a dyn Shape
and gives me:
the size for values of type
dyn Shape
cannot be known at compilation time the traitSized
is not implemented fordyn Shape
So instead I tried to implement my Data
struct like this:
struct Data2<P>
where
P: Shape,
{
shape: P
}
However, now I can no longer swap the shape
variable to a different Shape
.
Is there a way I can create a swappable shape
variable that can also be sent to the lib function lib_display_area
?
Full Example
trait Shape {
fn area(&self) -> f32;
}
struct Circle{
radius: f32,
}
impl Shape for Circle {
fn area(&self) -> f32 {
self.radius.powi(2) * std::f32::consts::PI
}
}
struct Square {
side: f32,
}
impl Shape for Square {
fn area(&self) -> f32 {
self.side.powi(2)
}
}
struct Data1
{
shape: Box<dyn Shape>
}
struct Data2<P>
where
P: Shape,
{
shape: P
}
fn main() {
let square = Square {side: 10.0};
let circle = Circle {radius: 10.0};
let data1 = Data1 { shape: Box::new(square) };
lib_display_area(data1.shape.as_ref());// E0277
let data = Data2 {shape: circle};
lib_display_area(data.shape);
data.shape = square; // E0308
lib_display_area(data.shape);
}
fn lib_display_area<P>(p: P)
where
P: Shape,
{
println!("area is {}", p.area())
}
You definitely have to use dyn
for this.
You can make this work, without changing lib_display_area
, by using a wrapper struct that implements Shape
and just delegates to the inner &dyn Shape
:
#[derive(Clone, Copy)]
struct DynShape<'a>(&'a dyn Shape);
impl<'a> Shape for DynShape<'a> {
fn area(&self) -> f32 {
self.0.area()
}
}
fn main() {
let square = Square {side: 10.0};
let circle = Circle {radius: 10.0};
let mut data1 = Data1 { shape: Box::new(square) };
lib_display_area(DynShape(data1.shape.as_ref()));
data1.shape = Box::new(circle);
lib_display_area(DynShape(data1.shape.as_ref()));
}
Previous Answer
You can make lib_display_area
work by taking a reference instead of taking shape by value:
fn lib_display_area<P: Shape + ?Sized>(p: &P) {
println!("area is {}", p.area())
}
The ?Sized
bound just says "the type P
can be unsized", which is necessary for dyn Trait
trait objects. Even though the trait object itself is unsized, a reference to it is still sized, so we can pass it around.
fn main() {
let square = Square { side: 10.0 };
let circle = Circle { radius: 10.0 };
let mut data1 = Data1 {
shape: Box::new(square),
};
lib_display_area(data1.shape.as_ref());
data1.shape = Box::new(circle);
lib_display_area(data1.shape.as_ref());
}