Course contentsShow
← Rust
Lesson 749 of 1,9106. Generics and Static DispatchFree lesson

The Strategy Pattern with Generics

Implement the strategy pattern using static dispatch to eliminate virtual method overhead.

In this lesson, you will learn how to implement the Strategy Pattern using static dispatch to achieve maximum performance by avoiding the overhead of trait objects.

The Strategy Pattern Explained

The Strategy Pattern is a design pattern that allows you to swap out algorithms or behaviors at runtime. Typically, this is done using Box<dyn Trait> (dynamic dispatch), which incurs a small performance penalty because the compiler must perform a lookup in a "vtable" to find the correct method to execute.

When we use Generics and Static Dispatch, we resolve the specific strategy at compile time. This allows the compiler to inline the strategy code, often leading to significantly faster execution.

Implementation with Generics

To implement this, instead of holding a trait object, our context struct holds a generic type T that is constrained by our strategy trait.

trait PaymentStrategy {
    fn pay(&self, amount: u32);
}

struct CreditCard;
impl PaymentStrategy for CreditCard {
    fn pay(&self, amount: u32) { println!("Paid {} via Card", amount); }
}

struct ShoppingCart<T: PaymentStrategy> {
    strategy: T,
}

impl<T: PaymentStrategy> ShoppingCart<T> {
    fn checkout(&self, amount: u32) {
        self.strategy.pay(amount);
    }
}

In this example, the ShoppingCart struct does not know the concrete type of T until you initialize it. If you create a ShoppingCart<CreditCard>, the Rust compiler generates a version of checkout specifically for CreditCard. Because the compiler knows exactly which pay method to call, it can eliminate the vtable overhead and inline the logic directly into the checkout call.

When to use this

Use static dispatch when you know which strategy you need at compile time or when you are working in a performance-critical loop where dynamic dispatch overhead would be unacceptable. If you need to switch strategies dynamically based on user input at runtime, you would revert to trait objects, but for fixed configurations, generics provide the most efficient approach.

Key Takeaway: By using generic trait bounds instead of trait objects, you enable the compiler to perform monomorphization and inlining, effectively removing runtime dispatch overhead.