I have been writing a Ray Caster in Rust following "The Ray Tracer Challenge", and I've been having a hard time figuring out the proper way to implement polymorphism in Rust. My priorities are that the object can be used in a multi-threaded program, and that seems to be the main problem.
I have two cases on this, but I'll focus on one: a shape. There are different kinds of shapes (sticking with the able
suffix I originally called my trait Intersectable
). Here was a working trait object implementation, but it didn't work with multi-threading:
#[derive(Debug)]
pub struct Shape {
pub parent: Option<Arc<Shape>>,
pub transform: Matrix4,
pub material: Material,
pub intersectable: Box<Intersectable>,
}
pub trait Intersectable: Debug + IntersectableClone {
fn local_intersect(&self, ray: &Ray, object: Arc<Shape>) -> Vec<Intersection>;
}
pub trait IntersectableClone {
fn clone_box(&self) -> Box<Intersectable>;
}
impl<T> IntersectableClone for T
where
T: 'static + Intersectable + Clone,
{
fn clone_box(&self) -> Box<Intersectable> {
Box::new(self.clone())
}
}
impl Clone for Box<Intersectable> {
fn clone(&self) -> Box<Intersectable> {
self.clone_box()
}
}
#[derive(Clone, Debug)]
pub struct Sphere {}
impl Intersectable for Sphere {
fn local_intersect(&self, ray: &Ray, object: Arc<Shape>) -> Vec<Intersection> {
...sphere specific code
}
}
#[derive(Clone, Debug)]
pub struct Plane {}
impl Intersectable for Plane {
fn local_intersect(&self, ray: &Ray, object: Arc<Shape>) -> Vec<Intersection> {
...plane specific code
}
}
As a pure struct, with no official polymorphism, I've written a kind of static dispatch that looks like this:
#[derive(Debug, Clone)]
pub enum IntersectableType {
Sphere,
Plane,
}
#[derive(Debug, Clone)]
pub struct Intersectable {
intersectable_type: IntersectableType,
}
impl Intersectable {
pub fn local_intersect(&self, ray: &Ray, object: Arc<Shape>) -> Vec<Intersection> {
match self.intersectable_type {
IntersectableType::Sphere => self.local_intersect_sphere(ray, object),
IntersectableType::Plane => self.local_intersect_plane(ray, object),
_ => Vec::new(),
}
}
fn local_intersect_sphere(&self, ray: &Ray, object: Arc<Shape>) -> Vec<Intersection> {
...sphere specific code
}
fn local_intersect_plane(&self, ray: &Ray, object: Arc<Shape>) -> Vec<Intersection> {
...plane specific implementation
}
}
This works great, but it feels very non-Rusty. I've hit a few problems with using other implementations:
- Using Box<Intersectable>
(when it was a Trait, not a struct), is difficult to clone (I copied How to clone a struct storing a boxed trait object? but didn't love having to use 'static, since that made concurrency impossible).
- Using Arc<Intersectable>
seemed to have the same problems as Box
, though maybe there is a way to make that work.
Is there a way to do this in Rust that allows me to take advantage of concurrency and not write manual static dispatch like this?