I was wondering if anyone knows what the relative performance implications of using a Box<Trait> vs having an enum that wraps a subset of types that implement Trait and delegates all methods to them.
Some code as an example, say there were 2 types to be supported both implementing std::io::Read:
struct Foo { ... }
struct Bar { ... }
impl Read for Foo { ... }
impl Read for Bar { ... }
enum Wrapper {
Foo(Foo),
Bar(Bar),
}
impl Read for Wrapper {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
match *self {
Wrapper::Foo(ref mut foo) => foo.read(buf),
Wrapper::Bar(ref mut bar) => bar.read(buf),
}
}
// ... delegation for the rest of the methods
}
For a small case like this I feel like the Wrapper::read function could probably be inlined and there would be very little overhead compared to the dynamic dispatch for a Box<Read>. However at some point as you add more types to support Wrapper::read itself will become too large to inline. I assume the performance overhead of delegating to a separate function that dispatches based on the enum tag will be comparable to the overhead of vtable based dynamic dispatch?
In the cases I am trying to decide which to use there are probably a half dozen types that need supporting, it’s a semi-open set of types where I don’t really need to be able to tell which type it is so using a Boxed trait would be fine, but since it’s a limited set I would also be fine with having to extend the enum and recompile to add a new type if that would be noticeably faster (definitely using a macro to implement the delegation).
I realise there is also the memory usage to take into consideration. I believe generally the memory usage of the different variants I will be supporting should be similar, and I will definitely be using #![warn(variant_size_differences)] to keep track of them. But if anyone has any experience/comments around potential issues with that I would appreciate them also.