Search code examples
rustpolymorphism

Swappable trait interface


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 trait Sized is not implemented for dyn 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())
}

Solution

  • 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());
    }