#mustache #expressions #bytecode #interpreter

bin+lib hairy

Compiled text templates (not unlike Mustache and Handlebars), with support for expressions and custom functions inside such expressions

4 releases

new 0.2.1 Mar 25, 2023
0.2.0 Mar 25, 2023
0.1.2 Jun 18, 2022
0.1.1 May 29, 2022
0.1.0 May 27, 2022

#47 in Template engine

Download history 28/week @ 2022-12-02 41/week @ 2022-12-09 55/week @ 2022-12-16 19/week @ 2022-12-23 6/week @ 2022-12-30 64/week @ 2023-01-06 33/week @ 2023-01-13 103/week @ 2023-01-20 109/week @ 2023-01-27 49/week @ 2023-02-03 47/week @ 2023-02-10 31/week @ 2023-02-17 24/week @ 2023-02-24 60/week @ 2023-03-03 101/week @ 2023-03-10 91/week @ 2023-03-17

285 downloads per month


12K SLoC


The hairy crate provides text templates, not unlike mustache and handlebars (and the earlier ctemplate original), but a bit different. Scoping is different: variables must specify explicitly which scope they use (by using a.b syntax). This is to avoid implicit behaviour when variables are (accidentally) overwritten. To catch errors early on, optionally types can be added, which are checked compile time. All errors are treated as hard errors and are reported. So missing values and typing errors are not silently ignored. Also, the templates support evaluation: {{=expression}}. The supported expressions are from the [expry] crate, which allows executing expression on binary encoded JSON-like values (with support for defining custom functions). Auto-escaping is applied to the output to avoid security issues (such as letting user input be exected as javascript).


In short the features and syntax:

  • Variables can be declared at top of an input file with type key: expry pairs (JSON is a valid expry). type can be either inline (value is replaced during compilation of the template) or default (which is a default value that is added to the evaluation time value if the key does not exist). The first non-parsable line signals the end of the variables.
  • Values can be outputted with {{=value}} (supports expressions, see below). Escape mode can be specified with {{=value:escape_mode}}. Values are processed by a user-definable escaper that can take the escape mode into account. Normally only strings and numbers are displayed, and the null value is considered a skip (without generating an error). Other values are considered an error, except when the sjs (script-js) or js escape modes are used.
  • Conditionals with {{if value}}contents{{end}}. Contents is displayed if value evaluates to true. An else construct is supported: {{if value}}foo{{else}}bar{{end}}.
  • Iterators with {{for variable in name}}content{{end}}, which can be used to iterate over (arrays of) any type. The contents of the array is available in the loop body under key variable;
  • Template definition with {{define name}}content{{end}}. Templates can have optional default values (in an object) with {{define name defaults object}}content{{end}}. Defaults are resolved at template compile time, using the global context given to the compile function;
  • Template instantiation with {{call name}} or {{call name with value}}. name can be an expression. If the name starts with a *, name can be an expression that resolves to a string (which is treated as a template name). If the name starts with **, name should be an expression that resolves to a binary form template code (as produced by the compile functions). If the with syntax is used, only the data specified in value is passed along (so the current context is not automatically transferred/merged, to do that use for value {...this, key: value}). This is done to avoid errors.
  • Error handling is different from normal Mustache: missing fields is always considered an error. Errors can be suppressed with the expr ??? alternative try syntax (on error in expr, alternative will be executed). Shorthand for expr ??? null is expr ???, which can be used in loops with {{for i in someField???}}, which ignores errors if the field is not found. Same for conditionals.

expry expressions can be used instead of values as above (think of expressions, functional calls, selectors, etc).

  • Mustache & Handlebars are the inspiration, however they lack the error handling of hairy (e.g. absent fields, type errors), and does not support expressions.
  • Ramhorns is a Mustache implementation in Rust (still lacking handling absent fields), but with a nice macro that allows using native Rust data structures as input for the templates.
  • Askama is a type-safe compiled templating engine written in Rust. It does feature checking for absent fields, but does not support dynamic templates.


~78K SLoC