# sailfish turns .stpl templates into Rust code through a derive macro

> sailfish is a small Rust template engine with EJS-like syntax, built-in filters and four template traits, one dependency and a Rust 1.89 floor. The catch is that template inheritance, the feature most template users expect, is still on the roadmap.

**rust-sailfish/sailfish** — Simple, small, and extremely fast template engine for Rust

- Repository: https://github.com/rust-sailfish/sailfish
- Website: https://rust-sailfish.github.io/sailfish/
- Stars: 995 · Forks: 64
- Language: Rust
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/rust-sailfish-sailfish

## Four traits, and the difference is how the data arrives

sailfish exposes four template traits, and the choice between them is about ownership rather than about syntax. TemplateSimple is the entry point most people start with: you derive it, point it at a template file, and read the fields directly. The simple version of the example renders a list of messages:

```rust
use sailfish::TemplateSimple;

#[derive(TemplateSimple)]
#[template(path = "hello.stpl")]
struct HelloTemplate {
    messages: Vec<String>
}

fn main() {
    let ctx = HelloTemplate {
        messages: vec![String::from("foo"), String::from("bar")],
    };
    println!("{}", ctx.render_once().unwrap());
}
```

Template renders through a shared reference instead, and the template reaches fields and methods through self, which is what you want when the data is expensive to clone or the template calls a method:

```rust
use sailfish::Template;

#[derive(Template)]
#[template(path = "hello.stpl")]
struct HelloTemplate {
    messages: Vec<String>
}

impl HelloTemplate {
    fn say_hello(&self) -> String {
        String::from("Hello!")
    }
}
```

The remaining two traits, TemplateMut and TemplateOnce, cover mutable and consuming templates that also need access to self, and the project links a page that compares all four. Picking the wrong one costs you a refactor, so it is worth reading that comparison before you settle on an API.

## The .stpl syntax is EJS with the same two tags

The syntax is inspired by EJS, and it uses the familiar pair of tags: a block tag for logic and an output tag for values. The template file in the example, templates/hello.stpl, loops over the messages and prints each one:

```erb
<html>
  <body>
    <% for msg in messages { %>
      <div><%= msg %></div>
    <% } %>
  </body>
</html>
```

There is no separate expression language to learn and no filter syntax bolted on top, which is part of why the crate can claim minimal dependencies. A template file carries the .stpl extension, and the extension appears in the path attribute of the derive macro rather than in a runtime configuration, so the link between a struct and its template is checked at compile time rather than assembled at run time. The template files live next to your code in the project directory, which means a rename that breaks a path is a compile error.

## A separate compiler crate is where the speed claim comes from

The project describes itself as simple, small and extremely fast, and it points at an external benchmark suite rather than publishing numbers in its own documentation, so the claim is easy to check elsewhere and hard to check here. The design that makes it fast is visible in the workspace layout, which splits the project into a sailfish library crate, a sailfish-compiler crate, a sailfish-macros crate and an integration test crate. The public API is derive-based, so the template is not parsed on every render: a macro attaches the template to your struct, and the separate compiler crate is where template text becomes Rust. That is a different cost profile from a runtime template parser, and it is the reason the crate can be both small and quick, but it also puts the template inside the build, which has consequences for how templates are deployed and reloaded.

## The dependency cost is one line, the Rust version is not negotiable

Adding sailfish to a project is a single entry in the dependency table:

```toml
[dependencies]
sailfish = "0.11.8"
```

The version in that snippet is the one the project documents, and the release history shows the same line moving quickly: v0.11.6 on 2026-09-11, v0.11.7 on 2026-09-14 and v0.11.8 on 2026-09-19. The floor that matters more is the language version. The project states that it works on Rust 1.89 or later, and the badge in the readme says the same, so an application on an older toolchain has to upgrade before it can adopt the engine. Minimal dependencies is a listed feature rather than a claim about the whole tree, and the practical reading is that the runtime side of the crate is small while the build side pulls in a compiler and a macro crate.

## Includes and filters are in, inheritance is not

The feature list covers including another template file inside a template, built-in filters, better error messages, and syntax highlighting support for vscode and vim, with the highlighting definitions kept in the syntax directory of the repository. The examples directory shows the same spread, with actix.rs, include.rs and simple.rs alongside a templates folder. What the list does not include is inheritance. The roadmap section names template inheritance, with block and partial support, as a future item, and the maintainer states plainly that the library is immature and that many planned features are at the stage of an RFC. That is the practical limit of the engine today. Blocks, partials and template composition are the features a team reaches for once a site has more than a handful of pages, and the ones that are not here yet are the ones that get hand-duplicated in the meantime.

## The alternative approach: parse the template at run time

The meaningful comparison is with a runtime template engine, the kind that reads template text and interprets it on each render, and the difference is where the work happens. sailfish moves the template into the build, which means a template error is a compile error, a template is a compiled artefact alongside the binary, and rendering does no parsing. A runtime engine keeps templates as data, which means they can be edited on disk, reloaded without a rebuild, and extended with late-bound features such as inheritance with block overriding, which is precisely the feature sailfish still lists as planned. Neither approach is right in general, but the trade is legible: build-time compilation suits a service that ships one template set and a fixed deployment, while runtime parsing suits an application where users or operators change templates after the binary is built. Sailfish has chosen the first, and the roadmap shows the cost of that choice.

## The repository is a library, a workspace and a documentation site

The tree shows what maintaining the project involves. The Cargo.toml at the root declares a workspace with the sailfish, sailfish-compiler, sailfish-macros and integration test members, and excludes the examples directory, so the examples are built separately from the library. There is a benches directory, a CHANGELOG.md, a Cargo.lock, and an assets folder, plus a netlify.toml and a runtime.txt for the documentation site. The docs are mkdocs projects, and the requirements file pins the toolchain tightly, with the mkdocs theme at 9.7.7, mkdocs at 1.2.3 and Pygments at 2.12 or later, and the readme ends with the commands used to build the site locally. The licence is MIT, the copyright runs from 2020, and the author is Ryohei Machida, who asks for design comments on the RFC labelled issues.

## Conclusion

sailfish fits Rust projects that render HTML or text from templates where startup cost and render latency matter more than template features, and where the team is willing to give up runtime-loaded templates. It does not fit work that needs template inheritance, block overriding or partials today, since that is on the roadmap rather than in the release, and it does not fit a project that needs to ship templates to users who can edit them after deployment. Before adopting it, check three things: the Rust version, since the project states it works on Rust 1.89 or later; the trait you need, because the four template traits differ in how they hand over the data, with TemplateSimple reading fields directly and Template going through a shared reference; and the build tooling on your side, since the library ships as a derive macro and the documentation site is built with mkdocs from a pinned requirements file. The last push was on 2026-10-13, and the current release is v0.11.8 from 2026-09-19.

## FAQ

### How do I add sailfish to a Rust project?

Add the crate to your dependency table, for example sailfish = "0.11.8", then derive a template trait on a struct and point the path attribute at a .stpl file in your templates directory. The engine then renders with render_once() for the simple trait or render() for the shared reference variant.

### What is the difference between TemplateSimple and Template in sailfish?

TemplateSimple lets the template read the struct fields directly and renders with render_once(). Template renders through a shared reference, so the template reaches fields and methods through self, and the example calls a say_hello method from the template. TemplateMut and TemplateOnce cover mutable and consuming templates.

### Does sailfish support template inheritance?

Not yet. Template inheritance, with blocks and partials, is listed on the project roadmap as a future item, and the maintainer notes that the library is still immature with many features at the RFC stage.

### What Rust version does sailfish require?

The project states that it works on Rust 1.89 or later, and the same version appears on the readme badge. The release line is still on 0.11.x, with v0.11.8 published on 2026-09-19.

### What syntax highlighting does sailfish ship?

Syntax highlighting definitions for vscode and vim are kept in the syntax directory of the repository. Templates use the .stpl extension, and the syntax is inspired by EJS.

## Sources

- [License: MIT](https://github.com/rust-sailfish/sailfish/blob/main/LICENSE)
- [Project website](https://rust-sailfish.github.io/sailfish/)
- [README](https://github.com/rust-sailfish/sailfish/blob/main/README.md)
- [Releases](https://github.com/rust-sailfish/sailfish/releases)
- [rust-sailfish/sailfish on GitHub](https://github.com/rust-sailfish/sailfish)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rust-sailfish-sailfish
