What a bunch of scary words! Today we are going to explore a way to create a text formatter without any runtime overhead, in Rust.
source: Image by Susanne Jutzeler - https://pixabay.com/photos/key-old-flower-nostalgic-vintage-5105878/ [free for commercial use]

I was trying to create a function that formats a few values, and I wanted to be able to change the way the formatting was done easily. Of course I wanted to be able to do all of that without any runtime cost. In a way, it’s a bit like HTML. You write the structure of the content, and then the formatting will be done using a CSS engine. The result will obviously be really different if you use a PC monitor or a braille reader but the structure is the same.

I want to thanks a lot the users of the Rust user forum, that helped me a lot in this process, especially daboros, KrishnaSannasi and mbrubeck. Without them I would not have been able to find such an elegant solution. Each time I ask for help on this forum, I am amazed how the Rust community is helpful, reactive and has advice more useful than what I was expecting.


The formatter will provide multiple formatting directives, like bold or strike. Since it’s an interface, we are going to create a trait. It should be lazy, and accept anything that can be formatted, so all functions will take an return object that implements Display. In this article, I will only implement a markdown formatter, but the architecture should allow to easily create an html formatter, or an ANSI formatter (if you want to display your fancy text in a terminal).

use std::fmt::Display;

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

Wait! I thought you can’t use impl Trait in the return of a trait method?
That’s right, and we will fix that later.

And now, we can use this trait to format our text.

fn usage<T: TextFormatter>(some_value: i32) {
    println!("A number in bold: {}", T::bold(some_value));
}

Author’s note: If the syntax coloration is completely brocken for you, I’m sorry. It look like the library I’m using is betraying me!


The types that implements TextFormatter don’t need any state. In Rust, there is two possibilities to create a zero-sized element. Variantless enum and stateless struct.

enum VariantLess{}
struct Stateless;

The difference between the two is that you can’t even instantiate a variantless enum. Since our API only need to know the type of the TextFormatter, and doesn’t need to manipulate an instance (there is no methods, only associated functions), then we will chose variantless enum to be clear that it will not be instantiated.

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

pub enum MarkdownFormatter{}

impl TextFormatter for MarkdownFormatter {
    fn bold<T: Display>(value: T) -> impl Display {
        struct FormattedBold<T: Display>(T);
        impl<T: Display> Display for FormattedBold<T> {
            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
                write!(f, "**{}**", self.0);
            }
        }
        FormattedBold(value)
    }

    fn strike<T: Display>(value: T) -> impl Display {
        // the implementation is left as an exercise for the reader!
    }
}

This is nice. We have something working, but the syntax is relatively heavy, and we will need to use the same pattern for every impl of the TextFormatter trait. There is surely a better way!


If you remember, in a previous post I presented the non virtual interface design pattern. We are going to use a similar technique to factorize the common parts.

use std::marker::PhantomData;

pub trait TextFormatter {
    fn bold<T: Display>(value: T) -> impl Display {
        struct FormattedBold<F: TextFormatter + ?Sized, T: Display>(T, PhantomData<F>);
        impl<F: TextFormatter, T: Display> Display for FormattedBold<F, T> {
            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
                F::bold_impl(f, &self.0)
            }
        }
        FormattedBold(value, PhantomData)
    }

    fn strike<T: Display>(value: T) -> impl Display { /* ... */ }

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

And now implementing the trait `TextFormatter is much easier:

impl TextFormatter for MarkdownFormatter {
    fn bold_impl<T: Display>(f: &mut Formatter<'_>, value: &T) -> fmt::Result {
        write!(f, "**{}**", value)
    }
    fn strike_impl<T: Display>(f: &mut Formatter<'_>, value: &T) -> fmt::Result {
        write!(f, "~~{}~~", value)
    }
}

There is still two things that bother me: the function bold_impl and strike_impl shouldn’t be public, and we still have an impl Display for the return type.

Everything in its own time! We are first going to adress the visibility issue. Unfortunately, and unlike anything else in rust, everything in a trait is public by default. Since there is no priv (private) keyword anymore, what we can use is a deriving from another trait in a private module.

trait TextFormatter: private::TextFormatterImpl {
    fn bold<T: Display>(value: T) -> impl Display {
        struct FormattedBold<F: Formatter + ?Sized, T: Display>(T, PhantomData<F>);
        impl<F: Formatter, T: Display> Display for FormattedBold<F, T> {
            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
                F::bold_impl(f, &self.0)
            }
        }
        FormattedBold(value, PhantomData)
    }

    fn strike<T: Display>(value: T) -> impl Display { /* ... */ }
}

// without `pub` this module is private
mod private {
    // but the trait TextFormatterImpl is visible in the rest of the crate
    pub trait TextFormatterImpl {
        fn bold_impl<T: Display>(f: &mut Formatter<'_>, value: &T) -> fmt::Result;
        fn strike_impl<T: Display>(f: &mut Formatter<'_>, value: &T) -> fmt::Result;
    }
}

And now the implementation of a TextFormatter becomes:

impl TextFormatter for MarkdownFormatter{}
impl private::TextFormatterImpl for MarkdownFormatter {
    fn bold_impl<T: Display>(f: &mut Formatter<'_>, value: &T) -> fmt::Result {
        write!(f, "**{}**", value)
    }
    fn strike_impl<T: Display>(f: &mut Formatter<'_>, value: &T) -> fmt::Result {
        write!(f, "~~{}~~", value)
    }
}

Functions outside of the current module will not be able to access to the implementation details, aka the bold_impl and strike_impl functions. It is not exactly like private inheritence, but it is really close.


And now that last bit. the impl in the return type of our trait. We are going to leak a bit of the implementation details to remove it (after all it’s why people are working on adding impl Trait in Trait!).

pub struct FormattedBold<F: TextFormatter + ?Sized, T: Display>(T, PhantomData<F>);
impl<F: TextFormatter, T: Display> Display for FormattedBold<F, T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        F::bold_impl(f, &self.0)
    }
}
// likewise for `FormattedStrike`

pub trait TextFormatter: private::TextFormatterImpl {
    fn bold<T: Display>(value: T) -> FormattedBold<Self, T> {
        FormattedBold(value, PhantomData)
    }

    // ...
}

And that's it!

As usual, you can find all the code in the playground.

Discuss-it on reddit.

Creative Commons License