New to Rust - having fun - and looking for some advise.
I'm implementing a 3d graphics system. The "scene" is a list of various kinds of objects (eg, Sphere, Box, Camera, Light). Then there are several modules for different sub-systems (viewing, rendering, simulation, etc). Each of these sub-systems define a trait which defined the API objects need to implement to participate in that module. For instance, the viewing sub-system defines "Viewable" which has a "draw()" function. The rendering system defined "Renderable" which has a "bounds()" function.
Ok, so each of those system have a list of objects which are registered with that system, like: objects : Vec<Box<dyn Renderable>>
So, now I want a list of all these objects no matter which sub-system traits they implement. Something like "Sphere" will implement all of them. "Camera" will only implement some of them. And then from this list I will want to be able to introspect "which objects in this list implement the X trait"? Pseudo code for rendering might be:
r is a renderer
for each object in scene.objects {
if object implement the trait "Renderable" {
r.add(object); // ?? r.add(object as Renderable)??
}
}
Questions
1: Am I thinking about this in a Rust-like way?
2: Can anyone suggest how to implement this?
I don't think this is ideal but my first thought was to simply define an is_renderable() fn in the Renderable trait (and so on for others).
The problem is that rust needs to know that every object in the list has this function, so I think every trait would need every is_trait() fn which gets tedious.
I also suspect you would actually need a single trait that everything implements, because to my knowledge you cannot have a bound like: T: Box<dyn Foo | Bar | Baz>, perhaps all the is_trait() fns could be in the "super trait". You might even be able to make some helpful blanket implementations for common groups of traits.
This is speculative on my part, I'm hoping there are better ways and looking forward to others' answers Thanks for the question.
Edit: I've read the question. Since each system only needs implementors of one trait you won't need a super trait to match the bounds. I think the is_trait() fn per trait might be enough.
My concern would be going back and forth between systems you (rust compiler) might lose sight of what traits each object implements as actual types get forgotten when returned into trait objects (dyn Trait) - I think!
Yeah, you can't do T: Box<dyn Foo | Bar | Baz> but I think something like this might get the same effect:
pub trait GraphicsTrait {}
pub trait Foo: GraphicsTrait {}
pub trait Bar: GraphicsTrait {}
pub trait Baz: GraphicsTrait {}
let x: Box<dyn GraphicsTrait> = // etc.
Might not be able to guarantee much about what a GraphicsTrait implemented object can do. But everything's just in Boxes on the heap, so well suited to Vecs.
Enums would probably be easier to work with. The only long term concern on those is if one of the variants becomes large, but with everything in Boxes they'll be nice and uniform.
Rust doesn't provide a mechanism to do this test, but it wouldn't be useful here in any case: Because they're all stored in the same container, every object here is the same type; the test would come out the same for every value. You'll need to provide some method on that type that provides the information yourself.
One way to do this is by extending @PFaas' solution some:
Now, if you have a dyn GraphicsTrait, you can access it as any of the more specific traits it supports. Unfortunately, there's no way to write a blanket impl for GraphicsTrait: You'll run into conflicting implementation errors pretty quickly. You can, however, write a macro to help:
With this macro in place, you can define your individual types like this:
struct OnlyFoo;
impl Foo for OnlyFoo {}
impl_graphics_trait!{ OnlyFoo: Foo }
struct FooAndBar;
impl Foo for FooAndBar {}
impl Bar for FooAndBar {}
impl_graphics_trait!{ FooAndBar: Foo, Bar }
(Playground)
NB: As written, the macro can't handle generics or being used outside the module it's defined. Both of those problems are fixable, but would have obscured the central point.