Skip to main content

Crate gotmpl

Crate gotmpl 

Source
Expand description

§gotmpl

Test GitHub License Crates.io Version docs.rs Crates.io MSRV

A Rust port of Go’s text/template.

Supports the full template syntax (pipelines, control flow, custom functions, template composition, whitespace trimming) with Go compatible output. no_std compatible (with alloc).

The crate forbids unsafe code and denies the panic-family lints (panic!, unwrap, expect, unreachable!, todo!, unimplemented!). User-provided functions that panic are caught under std; see no_std notes below.

§Quick start

use gotmpl::{Template, tmap};

let data = tmap! { "Name" => "World" };
let result = Template::new("hello")
    .parse("Hello, {{.Name}}!")
    .unwrap()
    .execute_to_string(&data)
    .unwrap();
assert_eq!(result, "Hello, World!");

For one-shot renders with no configuration, use gotmpl::execute (source string) or gotmpl::execute_file (reads from disk):

use gotmpl::{execute, tmap};

let result = execute("Hello, {{.Name}}!", &tmap! { "Name" => "World" }).unwrap();
assert_eq!(result, "Hello, World!");

§Template syntax

Actions are delimited by {{ and }} (configurable via .delims()).

§Data access

{{.}}              Current context (dot)
{{.Name}}          Field access on dot
{{.User.Email}}    Nested field access
{{$}}              Top-level data (root context)
{{$x}}             Variable access
{{$x.Name}}        Field access on variable

§Pipelines

{{.Name | printf "%s!"}}
{{"hello" | len | printf "%d chars"}}

§Control flow

{{if .Condition}}...{{end}}
{{if .Cond}}...{{else}}...{{end}}
{{if eq .X 1}}...{{else if eq .X 2}}...{{else}}...{{end}}

{{range .Items}}...{{end}}
{{range .Items}}...{{else}}empty{{end}}
{{range $i, $v := .Items}}{{$i}}: {{$v}}{{end}}
{{range .Items}}{{if eq . 3}}{{break}}{{end}}{{.}}{{end}}
{{range .Items}}{{if eq . 3}}{{continue}}{{end}}{{.}}{{end}}
{{range 5}}{{.}} {{end}}

{{with .Value}}...{{end}}
{{with .Value}}...{{else}}fallback{{end}}

§Variables

{{$x := .Name}}            Declare variable
{{$x = "new value"}}       Assign to existing variable
{{$i, $v := range .List}}  Range with index and value (declaration)
{{$i, $v = range .List}}   Range with index and value (assignment)

§Template composition

{{define "name"}}...{{end}}        Define a named template
{{template "name" .}}              Invoke a named template
{{block "name" .}}default{{end}}   Define and invoke (with default)

§Comments and whitespace trimming

{{/* This is a comment */}}
{{- .X}}     Trim whitespace before
{{.X -}}     Trim whitespace after
{{- .X -}}   Trim both sides

§Built-in functions

FunctionDescription
printConcatenate args (spaces between non-string adjacent args)
printfFormatted output (see below)
printlnPrint with spaces between args, trailing newline
lenLength of string, list, or map
indexIndex into list or map: index .List 0, index .Map "key"
sliceSlice a list or string: slice .List 1 3
callCall a function value: call .Func arg1 arg2
eq, ne, lt, le, gt, geComparison operators. eq supports multi-arg: eq .X 1 2 3
and, orShort-circuit logic, return the deciding value
notBoolean negation
html, js, urlqueryEscape for HTML, JavaScript, URL query

§printf verbs and flags

Format strings follow Go’s fmt syntax: %[flags][width][.precision][argument index]verb.

VerbApplies toOutput
%sstringThe string itself
%qstring, int(rune)Go-quoted string, or single-quoted rune literal
%vanyDefault formatted value
%dintDecimal
%bintBinary
%ointOctal
%x, %Xint, stringLower/upper hex (on strings: hex of each byte)
%cintUnicode scalar
%UintUnicode U+XXXX (with #: appends 'c')
%ffloatDecimal, no exponent
%e, %EfloatScientific notation (lower/upper e)
%g, %Gfloat%e/%E for large exponents, else %f
%tbooltrue / false
%%Literal %

Flags: - (left-align), + (always sign numerics), (leading space for non-negative numerics), # (alternate form: 0b/0o/0x/0X prefix, or quoted rune for %U), 0 (zero-pad numerics).

Width and .precision accept either a literal number or * / .* to read the value from the next argument (which must be an int — floats and other types yield %!(BADWIDTH) / %!(BADPREC)).

Argument indexing with %[N]verb selects the N-th (1-based) argument and resets the sequential cursor. Out-of-range or malformed indices produce %!verb(BADINDEX).

Mismatched verb/argument pairs produce Go’s error markers rather than panicking: %!v(BADVERB), %!v(MISSING), %!(EXTRA …) for unconsumed args, and the BADWIDTH / BADPREC / BADINDEX forms above.

§Custom functions

Register functions before parsing:

use gotmpl::{Template, tmap};
use gotmpl::Value;

let result = Template::new("test")
    .func("upper", |args| {
        match args.first() {
            Some(Value::String(s)) => Ok(Value::String(s.to_uppercase().into())),
            _ => Ok(Value::Nil),
        }
    })
    .parse("{{.Name | upper}}")
    .unwrap()
    .execute_to_string(&tmap! { "Name" => "hello" })
    .unwrap();
assert_eq!(result, "HELLO");

§Function values and call

Value::Function allows passing callable values through templates:

extern crate alloc;
use alloc::sync::Arc;
use gotmpl::{Template, tmap};
use gotmpl::{Value, ValueFunc};

let adder: ValueFunc = Arc::new(|args| {
    let sum: i64 = args.iter().filter_map(|a| a.as_int()).sum();
    Ok(Value::Int(sum))
});

let result = Template::new("test")
    .func("getAdder", move |_| Ok(Value::Function(adder.clone())))
    .parse("{{call (getAdder) 3 4}}")
    .unwrap()
    .execute_to_string(&tmap!{})
    .unwrap();
assert_eq!(result, "7");

§Options

use gotmpl::{Template, MissingKey};

let tmpl = Template::new("t")
    .missing_key(MissingKey::Error)   // error on missing map keys
    .delims("<<", ">>")              // custom delimiters
    .parse("<< .Name >>")
    .unwrap();

MissingKey implements FromStr, so you can parse from strings (useful for config files or CLI args):

use gotmpl::MissingKey;

let mk: MissingKey = "error".parse().unwrap();
MissingKey variantFromStr valueBehavior
Invalid (default)"invalid", "default"Return <no value>
ZeroValue"zero"Return <no value>
Error"error"Return an error

ZeroValue exists for parity with Go’s {{options "missingkey=zero"}} directive. Since Value is untyped, it behaves the same as Invalid; the variant is there so callers can still opt in to the named option.

§Number literals

Go-compatible number literal syntax:

{{42}}          Decimal
{{3.14}}        Float
{{0xFF}}        Hexadecimal
{{0o77}}        Octal
{{0377}}        Octal (legacy leading zero)
{{0b1010}}      Binary
{{1_000_000}}   Underscore separators
{{'a'}}         Character literal (97)
{{0x1.ep+2}}    Hex float (7.5)

§Data model

Template data uses the Value enum:

VariantRust typeGo equivalent
Niln/anil
Bool(bool)boolbool
Int(i64)i64int
Float(f64)f64float64
String(Arc<str>)Stringstring
List(Arc<[Value]>)Vec<Value>[]any
Map(Arc<BTreeMap<Arc<str>, Value>>)BTreeMapmap[string]any
Function(ValueFunc)Arc<dyn Fn>func(...)

The tmap! macro builds data maps:

use gotmpl::{tmap, ToValue};

let data = tmap! {
    "Name" => "Alice",
    "Age" => 30i64,
    "Scores" => vec![95i64, 87, 92],
    "Address" => tmap! {
        "City" => "Paris",
    },
};

§no_std support

The crate works in no_std environments (requires alloc). Disable the default std feature:

[dependencies]
gotmpl = { version = "0.3", default-features = false }

Without std, execute_fmt and execute_to_string are available. The io::Write-based execute/execute_template methods and parse_files require the std feature. User-defined functions that panic will propagate instead of being caught.

§Differences from Go

Rust has no runtime reflection, so:

  • No struct field access: use Value::Map instead
  • No method calls: register functions via .func()
  • No pointer/interface indirection: Value is always concrete
  • No complex numbers, channels, or iter.Seq
  • NaN comparisons return an error instead of Go’s silently wrong results

API shape is also a bit different:

  • Template::lookup returns the parsed body (Option<&ListNode>) rather than a re-executable *Template. To run a named definition, use execute_template_to_string("name", ...) on the parent.
  • Template::templates returns the list of defined names, not Template objects — definitions share the parent’s func map and options, so there’s no per-definition handle to hand back.
  • parse_files requires the files to be valid UTF-8. Go’s os.ReadFile + string(b) is a zero-copy reinterpret and accepts any bytes; we use std::fs::read_to_string, which validates.

A few formatting and slicing edge cases diverge too:

  • %#v falls back to %v. Go’s Go-syntax output ([]interface {}{1, 2, 3}, map[string]interface {}{"a":1}) needs concrete-type info we don’t carry.
  • %#U quotes some non-ASCII codepoints that Go skips. Our gate is char::is_control; Go uses the full strconv.IsPrint table, which also rejects non-ASCII spacing characters like U+00A0.
  • slice on a string at a mid-codepoint offset returns an error. Go would hand back the raw bytes (potentially invalid UTF-8); we can’t store invalid UTF-8 in Value::String, so we reject the slice instead.

§Go cross-check

The test suite can optionally run every template through Go’s text/template and assert output parity:

cargo test --features go-crosscheck

A Go helper (tests/testdata/go_crosscheck.go) is compiled once per test run. It reads templates and typed data from stdin as JSON, executes them via Go’s text/template, and prints the result.

Modules§

parse
Parser, lexer, and AST node types for the Go template language.

Macros§

tmap
Creates a Value::Map from key-value pairs, similar to Go’s map literals.

Structs§

Template
A parsed template, equivalent to Go’s template.Template.

Enums§

MissingKey
Controls behavior when accessing a missing key on a Value::Map. Set via Template::missing_key.
TemplateError
The error type returned by all template operations.
Value
The dynamic type for template data.

Traits§

ToValue
Trait for converting Rust types into template Values.

Functions§

execute
Parse and execute a template in one shot.
execute_filestd
Parse a template file and execute it in one shot.
html_escape
HTML-escape a string, replacing &, <, >, ", ', and NUL bytes.
is_true
Reports whether a Value is “true” according to Go’s template truthiness rules.
js_escape
JavaScript-escape a string for safe embedding in JS string literals.
url_encode
Percent-encode a string for use in URL query parameters (form-encoding).

Type Aliases§

FuncMap
A map from function names to template functions, equivalent to Go’s template.FuncMap. Pass one to Template::funcs to register several functions at once.
Result
Alias for Result<T, TemplateError>, the return type of every fallible operation in this crate.
ValueFunc
Alias for a callable function stored inside Value::Function.