Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple organization of Rust traits for "polymorphic" return

I have a basic struct called Frame that is useful for a bunch of calculations:.

pub struct Frame<T> {
    grid_val: Vec<T>,
    grid_space: Vec<[T; 2]>,
    calculated_result: Option<Vec<T>>
}

Frame can be used to describe most basic calculations, but sometimes there's more complicated issues that come up and I need to add some more geometric information. So I used composition for each geometry:

pub struct Sphere<T> {
    grid: Frame<T>,
    radius: T
}

pub struct Hyperbola<T> {
    top_grid: Frame<T>,
    bottom_grid: Frame<T>,
    internal_angle: T
}

Now I have a working implementation of Algorithm for Sphere:

pub trait Algorithm<T> {
    fn calculate_something(&self) -> Result<Sphere<T>, Error>
}

impl Algorithm<T> for Hyperbola {
    // do things with top_grid, bottom_grid, and internal_angle
}

impl Algorithm<T> for Sphere {
    // do things with grid and radius
}

This fills in calculated_result and returns a new Sphere. It's implemented this way because Algorithm needs to use the extra geometric information to compute the calculated_result — semantically, it makes more sense for it to be an implementation on the geometry, whose result happens to be associated with one or more Frames.

I want to implement the same Algorithm for Hyperbola. In fact, it's very close to the same, and it makes sense for the trait to be the same, but it doesn't make sense for it to return a Sphere<T>.

I know that I could add another trait like GeometricObject and add another layer of composition, but that seems excessive. I guess I could maybe use a Box, but that seems clumsy.

I also thought of having calculate_something return a Vec<T> to be inserted manually into whichever struct is in use, but then the ergonomics of returning the same struct type the method is called on are ruined (which is a waste in a public impl/trait).

How can I get this organized without making it traits all the way down?

like image 535
bright-star Avatar asked Feb 05 '23 11:02

bright-star


1 Answers

It appears you want an associated type:

pub trait Algorithm<T> {
    type Output;

    fn calculate_something(&self) -> Result<Self::Output, Error>;
}

impl<T> Algorithm<T> for Sphere<T> {
    type Output = Sphere<T>;

    fn calculate_something(&self) -> Result<Self::Output, Error> {
        unimplemented!()
    }
}

impl<T> Algorithm<T> for Hyperbola<T> {
    type Output = Hyperbola<T>;

    fn calculate_something(&self) -> Result<Self::Output, Error> {
        unimplemented!()
    }
}

Associated types are described in detail in The Rust Programming Language. I highly recommend reading through the entire book to become acquainted with what types of features Rust has to offer.

An alternate solution is to define another generic type on the trait:

pub trait Algorithm<T, Out = Self> {
    fn calculate_something(&self) -> Result<Out, Error>;
}

impl<T> Algorithm<T> for Sphere<T> {
    fn calculate_something(&self) -> Result<Sphere<T>, Error> {
        unimplemented!()
    }
}

impl<T> Algorithm<T> for Hyperbola<T> {
    fn calculate_something(&self) -> Result<Hyperbola<T>, Error> {
        unimplemented!()
    }
}

You then need to decide When is it appropriate to use an associated type versus a generic type?

like image 82
Shepmaster Avatar answered Feb 08 '23 14:02

Shepmaster