Something similar to virtual override to prevent code duplication

On C++ I've done some code like this:

class MessageHandler {
    virtual MediaPlayer::Ptr handle_message(...);
    virtual RtspServer::Ptr decideRtspServer(...);
    virtual RtspClient::Ptr decideRtspClient(...);
    virtual Decoder::Ptr decideDecoder(...);
    virtual Renderer::Ptr decideRenderer(...);
};

So, this class handles a message that informs which RtspServer, RtspClient, Decoder, Renderer to use, and returns a MediaPlayer. I want to write MessageHandlers for lots of devices: Android, Windows, Linux, etc, so I can override each decision. For example, I can override decideRenderer to decide an specific renderer that is only available in Linux, but the rest should not be overriden as it's the same in all devices.

I'm looking to do the same in Rust: have default decisions but the ability to "override" one if needed, when implementing a MessageHandler for an specific device.

How can I achieve something similar in Rust? I don't care if it looks like OOP or not, I just want to prevent rewriting the same code.

Try a default implementation on a trait: https://doc.rust-lang.org/book/ch10-02-traits.html#default-implementations

1 Like

Do those methods require your class to already have an instance of RtspServer and friends saved as a field, or are they creating new objects? Traits in Rust can't have fields like a virtual base class (they define behaviour, not data), so default implementations will only be able to return new objects.

Another idea is to split the MessageHandler's responsibilities out into different types (e.g. a DecoderDecider and a RtspServerDecider - although maybe with better names) and make the MessageHandler a facade which defers to the appropriate field. Then when creating the MessageHandler you instantiate it with objects that are specific to that platform. In the OO world this would be referred to as the strategy pattern.

Another thing to keep in mind is that if you have a large number of implementations and want to tweak bits and pieces of each implementation you'll need to write a lot of code regardless. Sometimes it leads to more readable and maintainable code to do a bit of copy-paste and be less clever. That way each implementation can be clean and you won't need to wrap your head around complex generic functions and #[cfg] attributes and families of traits because someone tried to squeeze the last drop of code reuse out of their codebase.

Of course you could also reuse code on an ad-hoc basis, pulling common bits out into their own functions and so on. But I feel like you are looking for a more general, cleaner solution.


Keep in mind that there's also a trade-off when writing code which uses platform-specific functionality while presenting a common interface to the user.

  • Leverage platform-specific functionality
  • Maintainability/readability
  • Code reuse

If you want to reuse code, especially when that code may only be implemented for a subset of platforms, you'll often need to sacrifice a bit of maintainability.

Depending on how big a component the MessageHandler is you may end up with something like this, which is quite similar to what the standard library does when providing a common interface to platform-specific functionality (see the std::sys and std::sys_common modules).

From my own experience, if you try to port OOP code straight to Rust, you're in a whole world of trouble. Rust doesn't think this way.

Instead, look deeply at your application's logic and separate different behaviors. Encapsulate each of them into a trait, then have types that implement these traits as necessary.

For example, Server<Rtsp> can be a trait, and you can simply do:

struct MessageHandler {
    decideRtspServer: Box<dyn Server<Rtsp>>
}