#compile-time #whitespace-control

oxiplate

Compile-time template engine with a focus on escaping, helpful error messages, and whitespace control

74 releases (16 breaking)

Uses new Rust 2024

0.17.0 Feb 20, 2026
0.16.1 Jan 26, 2026
0.15.8 Dec 31, 2025
0.11.7 Nov 28, 2025
0.1.5 Dec 31, 2024

#128 in Template engine

MIT/Apache

98KB
1.5K SLoC

Oxiplate

Latest Version MIT OR Apache-2.0 License MSRV Coverage Status Open Issues Repository Docs Build Status

Oxiplate is an experimental compile-time template system for Rust with a focus on helpful error messages, escaping, and whitespace control. Use at your own risk.

Using Oxiplate in your project

Hacking on Oxiplate

Helpful error messages

Position information is tracked across files and passed onto Rust. This results in debuggable error messages even when issues are caught by Rust instead of Oxiplate.

<h1>{{ title }}</h1>
<p>{{ message }}</p>
use oxiplate::prelude::*;

#[derive(Oxiplate)]
#[oxiplate = "add.html.oxip"]
struct Add {
    a: u32,
    b: u64,
}

let add = Add {
    a: 10,
    b: 9,
};

print!("{}", add.render()?);
#
# Ok::<(), ::std::fmt::Error>(())
error[E0308]: mismatched types
 --> ./templates/add.html.oxip:1:28
  |
1 | {{ a }} + {{ b }} = {{ a + b }}
  |                            ^ expected `u32`, found `u64`

error[E0277]: cannot add `u64` to `u32`
   --> ./templates/add.html.oxip:1:26
    |
  1 | {{ a }} + {{ b }} = {{ a + b }}
    |                          ^ no implementation for `u32 + u64`
    |
    = help: the trait `std::ops::Add<u64>` is not implemented for `u32`
[...]

Check out the broken tests directory of oxiplate and oxiplate-derive for (tested) example error messages.

Escaping

Escaping is arguably the most important feature of a template system. The escaper name appears first to make it easier to spot, and always runs last to ensure the output is always safe. Creating templates in a language not supported by Oxiplate? You can add your own escapers!

<!-- Profile link for {{ comment: name }} -->
<a href="{{ attr: url }}">{{ text: name }}</a>
use oxiplate::prelude::*;

#[derive(Oxiplate)]
#[oxiplate = "profile-link.html.oxip"]
struct ProfileLink {
    url: &'static str,
    name: &'static str,
}

let profile_link = ProfileLink {
    url: r#""><script>alert("hacked!");</script>"#,
    name: r#"<!-- --><script>alert("hacked!");</script><!-- -->"#
};

print!("{}", profile_link.render()?);
#
# // Update HTML code block as well if output changes.
# assert_eq!(
#     profile_link.render()?,
#     r#"<!-- Profile link for ‹ǃ−− −−›‹script›alert("hackedǃ");‹/script›‹ǃ−− −−› -->
# <a href="&#34;><script>alert(&#34;hacked!&#34;);</script>">&lt;!-- -->&lt;script>alert("hacked!");&lt;/script>&lt;!-- --></a>
# "#,
# );
#
# Ok::<(), ::std::fmt::Error>(())
<!-- Profile link for ‹ǃ−− −−›‹script›alert("hackedǃ");‹/script›‹ǃ−− −−› -->
<a href="&#34;><script>alert(&#34;hacked!&#34;);</script>">&lt;!-- -->&lt;script>alert("hacked!");&lt;/script>&lt;!-- --></a>

Read the full escaping chapter for more information.

Whitespace control

Oxiplate supports removing trailing/leading/surrounding whitespace, or even collapsing it down to a single space.

{# Say hi and bye -#}
<a href="#">{-}
    Hello {{ name -}}
</a>{_}
<a href="#">{-}
    Goodbye
    {{_ name -}}
</a>
<a href="#">Hello Bell</a> <a href="#">Goodbye Bell</a>

Read the full whitespace control chapter for more information.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~120–495KB
~12K SLoC