Templating engine? No thanks.

2021-11-12 · 2 minute read

Here is the Rust code that produces the page you're currently reading (at least at the time of writing). Some time ago, a friend of mine asked me why I used this home-grown templating mechanism instead of a standard like mustache.

Admittedly, this might look very hacky on first impression:

async fn article_full(article: &Article, suggestion: &Article) -> String {
    fs::read_to_string("assets/article-full.html")
        .await
        .unwrap()
        .fill_in_article(&article)
        .replace("{{ suggestion-key }}", &suggestion.key)
        .replace("{{ suggestion-title }}", &suggestion.title)
}

It's just loading the template file and then replacing some strings! (If you wonder about fill_in_article(&article), that also does nothing more than replacing some strings.)

But I still think it's more elegant than using some full-blown templating engine like mustache for two reasons:

  1. It's pure Rust. Depending on one less library means you have to understand one less library. In fact, you can understand this code just by having a rough grasp of the standard library. I'm no Rust magician myself, but I still believe this is the easiest to understand version of the code that can possibly exist, given no prior templating knowledge.

  2. It's type-safe! Instead of having some weird looking syntax like {{#condition}} stuff {{/condition}} in the HTML code, I instead take advantage of Rust's full type safety! Just take a look at how the main page is constructed:

     pub async fn blog_page(articles: Vec<Article>) -> String {
         let mut teasers = vec![];
         for article in articles {
             teasers.push(article_teaser(&article).await);
         }
         page(
             "Blog",
             &metadata(...),
             &itertools::join(teasers, "\n"),
         )
         .await
     }
    

    That's just marvelous! It takes a list of Articles, then turns each of them into some small HTML snippet using the article_teaser function and then it joins all of them and puts them in the body of a page. I can just apply my full knowledge of Rust's Iterator protocol without doing any weird hacks in HTML.

And it's not just me: Most modern UI frameworks – Flutter, React, Jetpack Compose, SwitftUI – try to move the power of constructing UI into the code itself instead of keeping it in a separate language.

So, the next time someone offers you a templating library, you might want to step back a bit and think: What's the benefit of adding this other abstraction? Is this really making my life easier overall?

You liked this article? Feel free to share it using this shortlink:

Looking for another article? This one might be interesting:

Reverse Engineering the Gira HomeServer