Implementing the strategy pattern in Rust

Hello!

This post is the conclusion of my previous question. I would like to gather feedback to the final implementation of the strategy pattern. EDIT: the strategy pattern should allow for client to be able to implement there own strategies (think function pointer, not switch/enum).

My criterias:

  • easy to use and hard/impossible to misuse
  • easy to implement
  • zero-cost abstraction

… take 3!


In C++ my preferred implementation of the strategy pattern is using NVI (Non Virtual Interface):

class BaseStategy {
public:
    // public, non-overridable
    void do_something() {
        // do some common stuff
        do_something_impl();
        // do some other common stuff
    }
private:
    // private, must be overriden
    virtual void do_something_impl() = 0;
}

class ImplementationStrategy: public BaseStrategy {
    void so_something_impl() override { /* ... */ }
}

Since what I want is a specific implementation of the Strategy pattern, I will take the example an example that should be relatively representative of a real example:

  • pass self to the inner function (it's technically not needed if we only need static dispatch here, but it's both needed for dynamic dispatch, and if we want to use the same template for another usage).
  • the implementation is non-trivial and uses a few helpers that are common to all implementations

First let's define the public interface

use std::fmt;
use std::fmt::{Display, Formatter};

pub trait TextFormatter
{
    fn bold<T: Display>(&self, value: T) -> impl Display;
    fn strike<T: Display>(&self, value: T) -> impl Display;
}

Since -> impl Trait isn't valid Rust syntax yet, let's re-write it. This is just a current limitation of the ergonomic of the Rust language and shouldn't be considered a real problem. The implementation details on how to do it is explained later.

This public interface allow anyone to add formatting to anything that implements display. Let's use it.

pub fn usage<T: TextFormatter>(formatter: &T, some_value: i32) {
    println!("A number in bold: {}", formatter.bold(some_value));
    println!("A number striked: {}", formatter.strike(some_value));
}

This function can only use the public interface of our TextFormatter and doesn't know any of the implementation which is perfect.

Now let's use this function (assuming that we have two types MarkdownFormatter and HtmlFormatter that implements TextFormatter:

pub fn main() {
    // prints
    // ```
    // A number in bold: **42**
    // A number stiked: ~~42~~
    // ```
    usage(&MarkdownFormatter, 42);

    // prints
    // ```
    // A number in bold: <strong>42</strong>
    // A number stiked: <del>42</del>
    // ```
    usage(&HtmlFormatter, 42);
}

So far, so good. The second task is to creates those implementation. First let's re-write the public interface in valid current Rust:

// public interface
pub struct FormattedBold<'a, F: TextFormatter + 'a + ?Sized, T: Display>(&'a F, T);
pub struct FormattedStrike<'a, F: TextFormatter + 'a + ?Sized, T: Display>(&'a F, T);
pub trait TextFormatter
{
    type Self_: private::TextFormatterImpl;

    fn bold<'a, T: Display + 'a>(&self, value: T) -> FormattedBold<Self::Self_, T>
    where
        FormattedBold<'a, Self::Self_, T>: Display + 'a,
        Self::Self_: 'a;

    fn strike<'a, T: Display + 'a>(&self, value: T) -> FormattedStrike<Self::Self_, T>
    where
        FormattedStrike<'a, Self::Self_, T>: Display + 'a,
        Self::Self_: 'a;
}

Implementing Display for FormattedBold and FormattedStrike is a bit repetitive, so we want to create helpers. Types that implements FormattedText should only need to implements those helper.

// private interface
pub trait TextFormatterImpl {
    fn bold_impl<T: Display>(&self, f: &mut Formatter<'_>, value: &T) -> fmt::Result;
    fn strike_impl<T: Display>(&self, f: &mut Formatter<'_>, value: &T) -> fmt::Result;
}

// common to all implementation of TextFormatter
impl<F: TextFormatterImpl, T: Display> Display for FormattedBold<'_, F, T>
{
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        F::bold_impl(&self.0, f, &self.1)
    }
}
impl<F: TextFormatterImpl, T: Display> Display for FormattedStrike<'_, F, T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        F::strike_impl(&self.0, f, &self.1)
    }
}

Each type that implements TextFormatterImpl should get TextFormatter for free, so let's add the glue:

impl <TF: TextFormatterImpl> TextFormatter for TF {
    type Self_ = Self;

    fn bold<'a, T: Display + 'a>(&self, value: T) -> FormattedBold<Self::Self_, T>
    where
        FormattedBold<'a, Self::Self_, T>: Display + 'a,
        Self::Self_: 'a,
    {
        FormattedBold(&self, value)
    }
    fn strike<'a, T: Display + 'a>(&self, value: T) -> FormattedStrike<Self::Self_, T>
    where
        FormattedStrike<'a, Self::Self_, T>: Display + 'a,
        Self::Self_: 'a, 
    {   
        FormattedStrike(&self, value)
    }   
}

And now the fun part, let's implement our concrete implementations:

// concrete implementation of TextFormatter
pub struct MarkdownFormatter;
impl TextFormatterImpl for MarkdownFormatter {
    fn bold_impl<T: Display>(&self, f: &mut Formatter<'_>, value: &T) -> fmt::Result {
        write!(f, "**{}**", value)
    }
    fn strike_impl<T: Display>(&self, f: &mut Formatter<'_>, value: &T) -> fmt::Result {
        write!(f, "~~{}~~", value)
    }
}

pub struct HtmlFormatter;
impl TextFormatterImpl for HtmlFormatter {
    fn bold_impl<T: Display>(&self, f: &mut Formatter<'_>, value: &T) -> fmt::Result {
        write!(f, "<bold>{}</bold>", value)
    }
    fn strike_impl<T: Display>(&self, f: &mut Formatter<'_>, value: &T) -> fmt::Result {
        write!(f, "<del>{}</del>", value)
    }
}

As you can see implementing a new text formatter is really easy.

playground
EDIT: I think it was a mistake to have the private implementation (TextFormatterImpl) in a non-public module since client should be able to implement their own version of TextFormatterImpl.


I would like to know what you think of this approach.

Personally what I like:

  • implementing a specialization of TextFormatter is really easy with TextFormatterImpl.
  • no implementation details are leaked is the public API (TextFormatter).

What I dislike:

  • it's not explicit that implementing TextFormatterImpl imply that you are in reality implementing a TextFormatter
  • it is possible to implement TextFormatter without implementing TextFormatterImpl which could have bad consequences for example if bold() or strike() had to do some business logic validation prior to call bold_impl()/strike_impl() and those validation could be skiped -> it's possible to mis-implement the public API.

Alternatively it would be possible to have TextFormatter that have TextFormatterImpl as super trait. It would solve my first pain point (the explicitness) at the cost of making the private API leaking witch I'm not a fan of.

It seems very complicated and I don't like TextFormatterImpl. It would seem at least somewhat more idiomatic to have bold_impl and strike_impl be the methods directly on the trait, and have the bold/strike methods be either default methods on the trait or methods on an extension trait.

2 Likes

Then you have the private implementation leaking and usage could call directly bold_impl() and strike_impl() witch is not something you want.

Ideally, function and types in trait should be – as anything in Rust – private by default.

pub trait TextFormatter
{
    // public function, visibly by the user of TextFormatter, like `usage()`
    pub fn bold<'a, T: Display + 'a>(&self, value: T) -> FormattedBold<Self::Self_, T>
    where
        FormattedBold<'a, Self::Self_, T>: Display + 'a,
    {
        /* default implementation */
    }

    // public function, visibly by the user of TextFormatter, like `usage()`
    pub fn strike<'a, T: Display + 'a>(&self, value: T) -> FormattedStrike<Self::Self_, T>
    /* ... same as bold()  */


    // private functions, must be implemented by types implementing TextFormatter
    // but invisible to the user of TextFormatter, like `usage()`
    fn bold_impl<T: Display>(&self, f: &mut Formatter<'_>, value: &T) -> fmt::Result;
    fn strike_impl<T: Display>(&self, f: &mut Formatter<'_>, value: &T) -> fmt::Result;
}

EDIT: I think it was an important detail, I added at the top of the initial post.

I agree, but I can't find a way to emulate this pattern from C++

class BaseStategy {
public:
    // public, non-overridable
    void do_something() {
        // do some common stuff
        do_something_impl();
        // do some other common stuff
    }
private:
    // private, must be overriden
    virtual void do_something_impl() = 0;
}

class ImplementationStrategy: public BaseStrategy {
    void so_something_impl() override { /* ... */ }
}

This is what I came up with: Playground

It uses dynamic dispatch, but you could make it generic I guess. The advantage is that the strategy can be selected at runtime, but the disadvantage is that the implementation trait must be object safe.

The main idea is to use a struct instead of a trait for the TextFormatter:

// A struct that can format something with different strategies
pub struct TextFormatter<'a> {
    strategy: &'a dyn TextFormatterImpl,
}

The trait implementing the strategies is private, so it can't be implemented in other modules:

trait TextFormatterImpl {
    fn bold_impl(&self, f: &mut Formatter<'_>, value: &dyn Display) -> fmt::Result;

    fn strike_impl(&self, f: &mut Formatter<'_>, value: &dyn Display) -> fmt::Result;
}

EDIT: Here's a Playground using generics.

1 Like

Thanks for your solution.

My first impression was that I thought it wasn't a good idea, because client can't implement their own formatter. I was a requirement that I forgot to express (and the fact that my playground link put it in a private module didn't help). But thinking back about it, this just means that TextFormatter should have a constructor that take a TextFormatterImpl, and then client can do exactly what they want. I will play a bit with your solution, but it's probably exactly what I wanted!

I came nearly to the same conclusion: playground.

The main difference is that the Bold and Strike trait are declared inside the function bold() and strike(), and TextFormatter is generic only on TextFormatterImpl.

Thanks a lot, I'm finally happy with my solution.

✓ easy to use and hard/impossible to misuse
✓ easy to implement
✓ zero-cost abstraction

✓ implementing a formatter is really easy with TextFormatterImpl .
✓ no implementation details are leaked is the public API ( TextFormatter ).
✓ it is not possible override the public methods of TextFormatter (unlike the initial version using a trait)

@alice could you please access the readability of this version :wink:

EDIT:
The only that I dislike is that when using a TextFormatter, we must be generic over `TextFormatterImpl:

pub fn usage<TF: TextFormatterImpl>(formatter: &TextFormatter<TF>, some_value: i32) { /* ... */ }

I guess there is no way to write something like

pub fn usage(formatter: &impl TextFormatter, some_value: i32) { /* ... */ }

EDIT2: It's not possible with current Rust. Work is being done, but will wait until the integration of chalk. See my other question.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.