Thanks to visit codestin.com
Credit goes to lib.rs

#golang #helm #templating

gtmpl-ng

The Golang Templating Language for Rust (fork with line number fix and Helm functions)

3 releases

new 0.7.5 Jan 7, 2026
0.7.4 Jan 6, 2026
0.7.2 Jan 6, 2026

#86 in Template engine

MIT license

350KB
10K SLoC

gtmpl-ng – Golang Templates for Rust

Latest Version


This is a fork of gtmpl-rust with some additional features and fixes.

gtmpl-ng provides the Golang text/template engine for Rust. This enables seamless integration of Rust application into the world of devops tools around kubernetes, docker and whatnot.

Getting Started

Add the following dependency to your Cargo manifest…

[dependencies]
gtmpl-ng = "0.7"

and look at the docs:

It's not perfect, yet. Help and feedback is more than welcome.

Some Examples

Basic template:

use gtmpl_ng as gtmpl;

fn main() {
    let output = gtmpl::template("Finally! Some {{ . }} for Rust", "gtmpl");
    assert_eq!(&output.unwrap(), "Finally! Some gtmpl for Rust");
}

Adding custom functions:

use gtmpl_value::Function;
use gtmpl_ng::{FuncError, gtmpl_fn, template, Value};

fn main() {
    gtmpl_fn!(
    fn add(a: u64, b: u64) -> Result<u64, FuncError> {
        Ok(a + b)
    });
    let equal = template(r#"{{ call . 1 2 }}"#, Value::Function(Function { f: add }));
    assert_eq!(&equal.unwrap(), "3");
}

Passing a struct as context:

use gtmpl_derive::Gtmpl;

#[derive(Gtmpl)]
struct Foo {
    bar: u8
}

fn main() {
    let foo = Foo { bar: 42 };
    let output = gtmpl_ng::template("The answer is: {{ .bar }}", foo);
    assert_eq!(&output.unwrap(), "The answer is: 42");
}

Invoking a method on a context:


use gtmpl_derive::Gtmpl;
use gtmpl_ng::{Func, FuncError, Value};

fn plus_one(args: &[Value]) -> Result<Value, FuncError> {
    if let Value::Object(ref o) = &args[0] {
        if let Some(Value::Number(ref n)) = o.get("num") {
            if let Some(i) = n.as_i64() {
                return Ok((i +1).into())
            }
        }
    }
    Err(anyhow!("integer required, got: {:?}", args))
}

#[derive(Gtmpl)]
struct AddMe {
    num: u8,
    plus_one: Func
}

fn main() {
    let add_me = AddMe { num: 42, plus_one };
    let output = gtmpl_ng::template("The answer is: {{ .plus_one }}", add_me);
    assert_eq!(&output.unwrap(), "The answer is: 43");
}

Current Limitations

This is work in progress. Currently the following features are not supported:

  • complex numbers
  • the following functions have not been implemented:
    • html, js
  • printf is not yet fully stable, but should support all sane input

Enhancements

Even though it was never intended to extend the syntax of Golang text/template there might be some convenient additions:

Helm Template Functions

Enable helm-functions to get 152 Helm-compatible template functions:

[dependencies.gtmpl-ng]
version = "0.7"
features = ["helm-functions"]
use gtmpl_ng::{Template, Context};
use gtmpl_ng::helm_functions::HELM_FUNCTIONS;

fn main() {
    let mut tmpl = Template::default();
    tmpl.add_funcs(&HELM_FUNCTIONS);
    tmpl.parse(r#"{{ "hello" | upper }}"#).unwrap();
    let output = tmpl.render(&Context::empty()).unwrap();
    assert_eq!(output, "HELLO");
}

Mows Template Functions

Enable mows-functions to get 3 additional mows-specific functions:

[dependencies.gtmpl-ng]
version = "0.7"
features = ["mows-functions"]
use gtmpl_ng::mows_functions::MOWS_FUNCTIONS;
// mowsRandomString, mowsDigest, mowsJoinDomain

All Functions

Enable all-functions to get both Helm and mows functions:

[dependencies.gtmpl-ng]
version = "0.7"
features = ["all-functions"]
use gtmpl_ng::all_functions::all_functions;

fn main() {
    let funcs = all_functions(); // Vec of all 155 functions
}

Dynamic Template

Enable gtmpl_dynamic_template in your Cargo.toml:

[dependencies.gtmpl-ng]
version = "0.7"
features = ["gtmpl_dynamic_template"]

Now you can have dynamic template names for the template action.

Example

use gtmpl_ng::{Context, Template};

fn main() {
    let mut template = Template::default();
    template
        .parse(
            r#"
            {{- define "tmpl1"}} some {{ end -}}
            {{- define "tmpl2"}} some other {{ end -}}
            there is {{- template (.) -}} template
            "#,
        )
        .unwrap();

    let context = Context::from("tmpl2");

    let output = template.render(&context);
    assert_eq!(output.unwrap(), "there is some other template".to_string());
}

The following syntax is used:

{{template (pipeline)}}
	The template with the name evaluated from the pipeline (parenthesized) is
    executed with nil data.

{{template (pipeline) pipeline}}
	The template with the name evaluated from the first pipeline (parenthesized)
    is executed with dot set to the value of the second pipeline.

Context

We use gtmpl_value's Value as internal data type. gtmpl_derive provides a handy derive macro to generate the From implementation for Value.

See:

Why do we need this?

Why? Dear god, why? I can already imagine the question coming up why anyone would ever do this. I wasn't a big fan of Golang templates when i first had to write some custom formatting strings for docker. Learning a new template language usually isn't something one is looking forward to. Most people avoid it completely. However, it's really useful for automation if you're looking for something more lightweight than a full blown DSL.

The main motivation for this is to make it easier to write devops tools in Rust that feel native. docker and helm (kubernetes) use golang templates and it feels more native if tooling around them uses the same.

Dependencies

~0.3–14MB
~133K SLoC