Search code examples
genericsrustdispatch

Rust dynamic dispatch on traits with generic parameters


I have the following Rust Playground permalink

which is from my following Ray Tracing in a Weekend.

At the point of implementing materials I chose to create a trait Material.

My thinking was that objects in the world would have a material attribute and upon a ray Hit the Hit can look at the object and ask for the material on demand. This is working fine for my Normal trait which follows a similar thinking. I implemented this all with dynamic dispatch although I think I grasp enough to have done it statically with trait bounds as well.

In the linked code, you see line 13 I'm requesting those which implement Normal have a method which will return a Material. This then suggests that now Normal is no longer eligible to be a trait object error[E0038]: the traitNormalcannot be made into an object

If I understand correctly from such questions as this it seems that since generics are monomorphized the Rust runtime could no feasibly leak up the appropriate method for material given there could ostensibly be one a different one for each type implementing Normal? This doesn't quite click with me as it seems that, put in the same position as the Rust runtime, I would be able to look at the Normal implementer I have in hand at a moment, say Sphere and say I will look in Sphere's vtable. Could you explain where I am wrong here?

From there, I tried to simply fight with the compiler and went to static dispatch. lines 17-21

struct Hit<'a> {
    point: Vec3,
    distance: f32,
    object: &'a dyn Normal,
}

became

struct Hit<'a, T: Normal> {
    point: Vec3,
    distance: f32,
    object: &'a T,
}

and from there I am left trying to plug hole after hole with what seems like no end in sight.

What design choices can I do differently in addition to learning what is fundamentally wrong with my current understanding?


Solution

  • I may be missing something, but I think you could - at least from what I've seen - follow your path further.

    I think you could change this function:

    fn material<T: Material>(&self) -> T;
    

    As it stands, it says: Any Normal offers a function material where the caller can specify a Material that the function will return.

    But (I think) you want to state is: Any Normal has a material that can be requested by the caller. But the caller has no right to dictate any Material that will be returned. To translate this to code, you could state:

    fn material(&self) -> &dyn Material;
    

    This tells that material returns a Material (as a trait object).

    Then, Sphere could implement Normal:

    impl<'a> Normal for Sphere<'a> {
        fn normal(&self, point: &Vec3) -> Ray {
            Ray::new(point, &(point - &self.center))
        }
        fn material(&self) -> &dyn Material {
            self.material
        }
    }
    

    Link to playground.