Advanced Traits

Advanced Trait-Based Abstractions

Advanced trait-based abstractions combine static dispatch, dynamic trait objects (dyn Trait), associated types, and adapter structs to build zero-cost composable pipelines (such as middleware stacks, query combinators, and type-erased plugin registries) that bridge generic compile-time performance with runtime flexibility.

Pattern 1: Zero-Cost Composable Middleware / Layer Pipelines

Instead of storing a Vec<Box<dyn Middleware>> that performs a heap allocation and vtable jump at every layer, a generic adapter struct Layered<Inner, Middleware> wraps an inner service by value. The compiler monomorphizes and inlines the entire pipeline into a single flat function call:

Static Composition (`Timeout<Logging<CoreService>>`):
+-------------------------------------------------------+
| TimeoutLayer                                          |
|   timeout_ms: u64                                     |
|   +-----------------------------------------------+   |
|   | LoggingLayer                                  |   |
|   |   prefix: &'static str                        |   |
|   |   +---------------------------------------+   |   |
|   |   | CoreHandler (0 heap pointers!)        |   |   |
|   |   +---------------------------------------+   |   |
|   +-----------------------------------------------+   |
+-------------------------------------------------------+
pub trait Handler {
    type Request;
    type Response;

    fn handle(&self, req: Self::Request) -> Self::Response;
}

// Extension trait for fluent `.with_logging(...)` composition:
pub trait HandlerExt: Handler + Sized {
    fn with_logging(self, tag: &'static str) -> Logged<Self> {
        Logged { inner: self, tag }
    }
}
impl<H: Handler> HandlerExt for H {}

pub struct Logged<H> {
    inner: H,
    tag: &'static str,
}

impl<H> Handler for Logged<H>
where
    H: Handler,
    H::Request: std::fmt::Debug,
    H::Response: std::fmt::Debug,
{
    type Request = H::Request;
    type Response = H::Response;

    fn handle(&self, req: Self::Request) -> Self::Response {
        println!("[{}] -> request: {:?}", self.tag, req);
        let resp = self.inner.handle(req);
        println!("[{}] <- response: {:?}", self.tag, resp);
        resp
    }
}

struct SquareService;
impl Handler for SquareService {
    type Request = i32;
    type Response = i32;

    fn handle(&self, req: i32) -> i32 {
        req * req
    }
}

fn main() {
    let stack = SquareService.with_logging("outer").with_logging("inner");
    println!("size of composed stack: {} bytes", size_of_val(&stack));
    let out = stack.handle(9);
    println!("final output: {}", out);
}
size of composed stack: 32 bytes
[inner] -> request: 9
[outer] -> request: 9
[outer] <- response: 81
[inner] <- response: 81
final output: 81

Pattern 2: Bridging Non-Dyn-Compatible Traits into dyn Trait (Type-Erased Bridge)

When a trait is not dyn-compatible—for example, Clone (because fn clone(&self) -> Self returns Self by value) or a trait with associated types—you can bridge it into a dyn-compatible trait by adding a helper trait DynClone whose method returns Box<dyn MyTrait> and providing a blanket implementation for all T: MyTrait + Clone:

trait Job: DynCloneJob {
    fn run(&self) -> String;
}

trait DynCloneJob {
    fn clone_box(&self) -> Box<dyn Job>;
}

// Blanket implementation automatically implements `DynCloneJob` for every
// concrete `T` that implements `Job + Clone + 'static`!
impl<T> DynCloneJob for T
where
    T: Job + Clone + 'static,
{
    fn clone_box(&self) -> Box<dyn Job> {
        Box::new(self.clone())
    }
}

// Now we can implement standard `Clone` for `Box<dyn Job>`!
impl Clone for Box<dyn Job> {
    fn clone(&self) -> Self {
        self.clone_box()
    }
}

#[derive(Clone)]
struct ReportJob {
    title: String,
}

impl Job for ReportJob {
    fn run(&self) -> String {
        format!("running report {:?}", self.title)
    }
}

fn main() {
    let original: Box<dyn Job> = Box::new(ReportJob {
        title: String::from("Q4 Revenue"),
    });

    // `Box<dyn Job>` can now be cloned directly via `.clone()`!
    let cloned: Box<dyn Job> = original.clone();

    println!("original: {}", original.run());
    println!("cloned:   {}", cloned.run());
}
original: running report "Q4 Revenue"
cloned:   running report "Q4 Revenue"