Course contentsShow
← Rust
Lesson 1567 of 1,91013. Metaprogramming with MacrosFree lesson

Implementing Decorator Patterns

You will inject logging and performance instrumentation into functions using method-attribute macros.

In this lesson, you will learn how to implement the Decorator Pattern by using attribute-like procedural macros to inject cross-cutting concerns like logging and performance instrumentation into existing functions.

The Decorator Pattern via Macros

In object-oriented languages, the Decorator Pattern often involves wrapping objects in multiple layers. In Rust, we achieve this using attribute macros. Because attributes are applied at compile time, they act as "decorators" that transform the AST of a function, allowing us to wrap the function body with setup and teardown logic without modifying the original implementation.

Practical Implementation: Instrumentation

Suppose you want to time every function execution without cluttering your business logic with Instant::now() calls. Instead of manually wrapping each function, we can build a #[timed] attribute macro that injects timing boilerplate.

When you decorate a function like this:

#[timed]
fn process_data(input: Vec<u8>) -> usize {
    // Business logic here
    input.len()
}

The macro intercepts the function AST and rewrites it into something resembling this:

fn process_data(input: Vec<u8>) -> usize {
    let _start = std::time::Instant::now();
    let _result = {
        // Original body injected here
        let input = input;
        input.len()
    };
    println!("Function took {:?}", _start.elapsed());
    _result
}

Why this works for advanced Rust

By manipulating the syn and quote crates, the macro parses the ItemFn, identifies the block of code inside the function, and wraps that block in a closure or scope. This approach is superior to manual decorators because it is declarative, type-safe, and zero-cost; the "wrapping" happens during compilation, meaning there is no dynamic dispatch or heap allocation penalty at runtime.

This pattern is essential for separating infrastructure concerns (logging, metrics, caching) from domain logic, keeping your codebase clean while ensuring consistent performance monitoring across your entire application.

Key Takeaway: Use attribute macros to inject cross-cutting logic into functions at compile-time, effectively creating a zero-cost Decorator Pattern that keeps your business logic clean and maintainable.