_______._ _ _ _.___/ | | | | | -----+----- ___ ___ | ______ ___ / /____ \ __________ _-+-_____/ .( .\_(xhb)__| |( \ | /. |.. \.. ___/.\ . ) ..\ . ____\ | o / | | |.. ). |. \ .|.( ...|.(. : (____| | | (______/.. |. \___.|______ . ______|.. | 9| ).. |. | .. |.. \ . | 9[ ( . o |. | .\ |. |. ___/ (| ] \___________/.. |\_______/________/\_________\ [ |_______________| ____________________________] : | - -----andrzej-_ ____\_____|----_|_____________'-lichnerowicz-------- - : | . p e r s o n a l ! | #1 | wEBPAGE . . _ - --------- | - -- - | ------- - _ . -:-\-\-------------------|- - - -|-----------------------------------\-\-: | _|___________|_ | | : : |
|//_)-> 2 0 2 5 . 0 2 . 0 4 <--------------------------------------------(_\|
| |
| |
| |
# Rust Inherited

I was working on a plugin system the other day and ran into an interesting problem. Imagine you're building a distributed map/reduce framework. The core operations -- , , and -- are conceptually the same, but each function is executed as a separate binary by the platform. This binary reads incoming data, transforms it into a , does its thing, and then returns a for serialization and further processing.

Most of the logic is shared: serialization, deserialization, protocol handling, maybe even some keepalives. But there are also unique requirements for each function—for example, needs to maintain an accumulator.

From the start, I had a few goals in mind:

  1. The SDK should abstract away all protocol handling, exposing only the core data processing functionality.
  2. The setup should be minimal.
  3. The design should be self-explanatory.
  4. It should be foolproof -- each plugin type (, , ) should be mutually exclusive.
## The OOP Approach

If I were doing this in Python or C++, I'd reach for inheritance. I'd create a base class with most of the functionality and subclass it for , , and . Something like this:

class Plugin:
def process(self):
self.protocol_handshake()
input = self.receive_records()
output = self.handle_records(input)
if output:
self.send_records(output)
def handle_records(self, input):
raise NotImplementedError
class Filter(Plugin):)
def handle_records(self, input):
return self.filter(input)
def filter(self, input):
raise NotImplementedError
class Map(Plugin):
def handle_records(self, input):
return self.map(input)
def map(self, input):
raise NotImplementedError

A developer using this SDK would just subclass one of these:

from sdk import Filter
class MyGreenGrassFilter(Filter):
def filter(self, input):
return None if 'failed' in input else input

This is clean, easy to understand, and exactly what I’d want from a developer’s perspective. The only issue? There’s no real way to enforce that a plugin is only a , , or . Someone could mix them up, and things could get messy.

## Rust Doesn’t Do That

Rust doesn’t have inheritance, nor does it let you implement traits for traits, like this:

trait Plugin {
fn handle_records(&self, input: Vec<String>) -> Vec<String>;
}
trait Filter {
fn filter(&self, input: Vec<String>) -> Vec<String>;
}
impl Plugin for Filter {
fn handle_records(&self, input: Vec<String>) -> Vec<String> {
self.filter(input)
}
}

That just doesn’t work. Rust traits don’t stack like that.

I tried getting around this with generics:

trait Plugin {
fn handle_records(&self, input: Vec<String>) -> Vec<String>;
}
trait Filter {
fn filter(&self, input: Vec<String>) -> Vec<String>;
}
impl<X> Plugin for X
where
X: Filter
{
fn handle_records(&self, input: Vec<String>) -> Vec<String> {
self.filter(input)
}
}

That worked... until I needed another command:

trait Map {
fn map(&self, input: Vec<String>) -> Vec<String>;
}
impl<X> Plugin for X
where
X: Map
{
fn handle_records(&self, input: Vec<String>) -> Vec<String> {
self.map(input)
}
}

Suddenly, Rust yelled at me:

sdk on  main [?] is 🦀 v1.86.0-nightly took 12s
❯ cargo build
Compiling sdk v0.1.0 (/Users/angelo/sdk)
error[E0119]: conflicting implementations of trait `Plugin`
--> src/main.rs:22:1
|
9 | / impl<X> Plugin for X
10 | | where
11 | | X: Filter
| |___________- first implementation here
...
22 | / impl<X> Plugin for X
23 | | where
24 | | X: Map
| |________^ conflicting implementation
For more information about this error, try `rustc --explain E0119`.
error: could not compile `sdk` (bin "sdk") due to 1 previous error

Rust was absolutely correct -- I couldn’t guarantee that and wouldn't be implemented on the same type. And Rust really cares about safety.

## The Solution: Enum-based Plugins

After a bit of soul-searching, I landed on this approach:

type Records = Vec<String>;
pub enum PluginType {
Filter(Box<dyn Filter>),
Map(Box<dyn Map>),
}
pub trait Plugin {
fn handle_records(&self, input: &Records) -> Result<Records>;
fn run() {
//...
}
}
impl Plugin for PluginType {
fn handle_records(&self, input: &Records) -> Result<Records> {
match self {
PluginType::Filter(v) => v.filter(input),
PluginType::Map(v) => v.map(records),
}
}
}
pub trait Filter {
fn filter(&self, input: &Records) -> Records;
}
pub trait Map {
fn map(&self, input: &Records) -> Records;
}
pub fn filter_plugin<F>(plugin: F) -> PluginType
where
F: Filter + 'static
{
PluginType::Filter(Box::new(plugin))
}
pub fn map_plugin<M>(plugin: M) -> PluginType
where
M: Map + 'static
{
PluginType::Map(Box::new(plugin))
}

And for the developer, it looks just as clean as the Python version:

use sdk::{Filter, filter_plugin};
struct Passthrough;
impl Filter for Passthrough {
fn filter(&self, input: Vec<String>) -> Vec<String> {
input
}
}
fn main() {
const plugin = filter_plugin(Passthrough);
plugin.run();
}
## The Best Part?

Not only does this keep the API as clean as the OOP version, but it also ensures mutual exclusivity between plugin types! A type cannot be both a Filter and a Map -- Rust enforces this at compile time.

So while Rust might not have traditional inheritance, with a little creativity (and a few enums), you can still get a clean and safe design.

| |
| |
| |
\__ --> andrzej.lichnerowicz.pl <-- __/ // \\ // ------------------------ ---------------------- \\ '~~~~~~~~~~~~~~~~~~~~~~~~~// ------ ------- \~~~~~~~~~~~~~~~~~~~~~~` '~~~~~~~// \~~~~~~~` // ---------- \ '~~~~~~~~~~~~~~~`