Skip to content
Select theme

How to replace placeholders in a template

Short answer: str::replace swaps every occurrence of one substring for another, so a chain of calls fills a template with a few slots: template.replace("{name}", &name).replace("{count}", &count). That is the whole answer when the template and the values are yours. A real templating crate pays once the template needs conditionals or loops, or arrives from somewhere you don’t control.

From a fresh project — cargo new template-demo — replace src/main.rs with each block below and run it with cargo run. The project name only shows up in cargo’s own output; nothing else on this page depends on it.

One call per slot, chained. replace takes a plain &str pattern — no regex, no escaping rules — and hands back a String.

Hello, Ada! You have 3 new messages.
value and value

Every occurrence goes, not only the first; replacen takes a count when you want fewer. The regex article ends by handing this job over: a pattern finds variable text, replace substitutes fixed slots.

Each call works on the string as it stands at that moment, and the chain cannot tell which braces came from the template and which came from a value:

done then done

The value substituted for {a} is itself a {b} slot, so the second call fills it and the first slot ends up as done as well. With {key}-style slots the closing brace keeps one key from swallowing the front of a longer one, so keys sharing a prefix are safe. The trap needs a value containing braces — a config file, a form field, anything you did not type. When values come from such a place, fill in one pass instead.

Walk the template once: copy the text up to each {, substitute at the slot, carry on after the }. Nothing that goes into the output is scanned again, so a value containing braces survives untouched.

Hello, Ada! You have 3 new messages.
{b} then done
unknown {key} stays
unclosed {slot

Line two is the point: the {b} that came out of the values stays a {b}. An unknown key is left as written and a { with no closing } is copied through — choices, not requirements; a template from a config file argues for the stricter version that returns a Result and names the key it could not resolve. With a dozen keys, a HashMap<&str, &str> replaces the slice scan.

replace runs out when the template grows a feature it cannot express. Conditionals and loops are the usual trigger — {% if %}, {% for %} — and no chain of calls grows those. Error handling is next: fill above lets an unknown key through on purpose.

Tera and Handlebars are the two you’ll meet most — Tera’s syntax is the Jinja2/Django one, Handlebars’ is its own. Both parse the whole template up front and refuse a broken one, and Handlebars escapes every value it substitutes by default, so HTML output is the other reason to hand the job over. Both are ordinary dependencies; nothing about this page changes except how much template language you get.

If the template is a literal in your own source, none of this applies: format!("Hello, {name}!") checks the slots at compile time, which is what the formatting article covers.