Course contentsShow
← Rust
Lesson 620 of 1,9105. Type System and TraitsFree lesson

Stateful vs Stateless Trait Architectures

Decide when to embed state into your trait implementations and when to keep them stateless.

In this lesson, you will learn how to decide whether your trait implementations should own data (stateful) or merely define behavior for external data (stateless).

The Stateless Approach

A stateless trait acts as a pure strategy or capability. It provides logic that operates entirely on arguments passed to its methods, without maintaining internal memory. This is common when implementing patterns like the Strategy Pattern.

trait Formatter {
    fn format(&self, input: &str) -> String;
}

struct UppercaseFormatter;
impl Formatter for UppercaseFormatter {
    fn format(&self, input: &str) -> String {
        input.to_uppercase()
    }
}

Here, UppercaseFormatter is a "unit struct" with no fields. It is stateless and zero-sized, making it extremely efficient to pass around. Use this approach when your trait represents a pure transformation or an algorithm that doesn’t need to "remember" anything between calls.

The Stateful Approach

A stateful trait implementation holds onto data, often to track progress, configuration, or system resources. This is necessary when your trait must maintain an internal context to satisfy a requirement, such as the Command Pattern or Provider Pattern.

struct CounterFormatter {
    count: std::cell::Cell<usize>,
}

impl Formatter for CounterFormatter {
    fn format(&self, input: &str) -> String {
        let current = self.count.get();
        self.count.set(current + 1);
        format!("{}: {}", current, input.to_uppercase())
    }
}

In this example, the trait implementation carries state (count). This is required when the behavior must change over time or based on previous interactions.

How to Decide?

Choosing between these architectures comes down to the requirements of the system:

  1. Prefer Stateless when you need concurrency safety (since shared immutable state is trivial) and memory efficiency. Stateless traits are inherently "reentrant" because they don’t mutate internal buffers or counters.
  2. Prefer Stateful when your trait needs to encapsulate complexity, track history, or manage a resource (like a database connection handle) that cannot be passed in as a method argument every time.

If you find yourself using RefCell or Mutex inside a stateless trait to "cheat" and add state, it is usually a sign that your architecture should be explicitly stateful from the start.

Key Takeaway: Choose stateless architectures for pure algorithmic transformations and stateful architectures when your behavior relies on a persistent internal context.