Architecture design: a general `enum` type with "subtypes" in different crates implementing a common trait

We would like to create an architecture with some trait Shape that allows us to calculate the area of a shape depending on some context. We allow each Shape to have its own type for scalars (e.g. radius type for circle, side type for square, etc.).

trait ShapeTypes {
    type Scalar;
}

trait Shape: ShapeTypes {
    fn area<Ctx: ShapeTypes>(&self, unit: Ctx::Scalar) -> Ctx::Scalar
    where
        Ctx::Scalar: From<Self::Scalar>;
}

As an example, let's take Circle with radius of type f64:

struct Circle(f64);

impl ShapeTypes for Circle {
    type Scalar = f64;
}

impl Shape for Circle {
    fn area<Ctx: ShapeTypes>(&self, unit: Ctx::Scalar) -> Ctx::Scalar
    where
        Ctx::Scalar: From<Self::Scalar>,
    {
        // here we would perform something like
        // unit * self.0.into()
        unimplemented!()
    }
}

At some point, we'll have multiple types of shapes and it'd be good to have some general structure for them: AnyShape implementing the Shape trait as well.

enum AnyShape {
    Circle(Circle),
}

impl Shape for AnyShape {
    fn area<Ctx: ShapeTypes>(&self, unit: Ctx::Scalar) -> Ctx::Scalar
    where
        Ctx::Scalar: From<Self::Scalar>,
    {
        match self {
            AnyShape::Circle(circle) => circle.area::<Ctx>(unit),
        }
    }
}

Also, at some point, we'd like to separate the shapes into their own crates so we can test their implementations separately.
The issue is that Rust doesn't allow the naive solution to this problem, so when trying to implement the Shape trait for the AnyShape structure for delegating calls to the sub-types, the error appears:
the trait bound `<Ctx as ShapeTypes>::Num: From<f64>` is not satisfied.

Link to playground.

How would one approach this in Rust?

I don't quite get what you are trying to do, or what you mean by "the naive solution". I think you simply typo'd: if you change the Scalar associated type from AnyShapeType to f64, the code compiles.

The thing is that there also might be other shapes like

struct Square(u32);

So our AnyShapeType would look like this:

enum AnyShapeType {
    Circle(<Circle as ShapeTypes>::Scalar), // f64
    Square(<Square as ShapeTypes>::Scalar), // u32
}

And the Context's Scalar type can be e.g. i128.

So what should happen in the case where the context has a different scalar type from that of the actual shape? It doesn't look like the From bound makes sense here.

Yes, the question is basically how to unify the types of the Ctx (passed from outside) and all of the shape types in calculations.

You can passa generic parameter to the AnyShapeType enum.

Yes, but it's best to avoid it.

Something like this could potentially work:

This one seems to work pretty well. Will try to use it.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.