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 Frame
s.
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?
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With