zip_templates

Crates.iozip_templates
lib.rszip_templates
version0.1.2
created_at2025-11-23 07:18:10.250478+00
updated_at2025-11-23 07:18:10.250478+00
descriptionFastest, simplest template rendering engine in rust, implementing a novel algorithm.
homepage
repositoryhttps://github.com/rathod-sahaab/zip-templates
max_upload_size
id1946221
size61,974
Abhay Raj Singh (rathod-sahaab)

documentation

https://docs.rs/zip-templates

README

ZipTemplates

ZipTemplates is a tiny, fast templating approach for rendering runtime-specified template strings.

The core idea is simple:

  • Parse a template string into two parallel arrays: statics (the literal parts) and placeholders (the variable slots).
  • When rendering, map placeholders to their substituted values, "zip" the placeholders with the statics array, and join the interleaved pieces to produce the final string.

This yields a predictable, low-allocation rendering approach suitable for high-throughput or runtime-driven templating scenarios.

Why ZipTemplates

  • Minimal runtime overhead: rendering is a single pass that zips two arrays and joins the pieces.
  • Low allocations: avoids building many intermediate strings while concatenating multiple parts.
  • Simple to implement and reason about.
  • Works well when the template is provided at runtime and templates are simple (interpolation only).

Concept / Contract

  • Input: a template string containing placeholder markers (e.g. {{0}}, $0, or any chosen syntax), and a mapping/array of substitution values to fill placeholders.
  • Output: a single rendered string with placeholders replaced by their corresponding values (coerced to strings).
  • Errors: if a placeholder index is missing, behavior is either (configurable) to insert an empty string or raise/return an error. The library should document and provide an option for strict mode.
  • Complexity: parse O(n) over template length; render O(p) where p is number of placeholders (with a final join cost dependent on output length).

Example (conceptual)

This is a language-agnostic example showing the algorithm.

  1. Parse a template into statics and placeholders arrays.

Template: "Hello, {{0}}! You have {{1}} new messages."

  • statics => ["Hello, ", "! You have ", " new messages."]
  • placeholders => ["0", "1"]
  1. Render by mapping placeholders to values and zipping:

Values array: ["Alice", 5]

Zipped pieces: ["Hello, ", "Alice", "! You have ", "5", " new messages."]

Join => "Hello, Alice! You have 5 new messages."

Named / nested placeholders example:

Template: "Hi, {{user.name.first}} — balance: {{account.balance}} USD"

  • statics => ["Hi, ", " — balance: ", " USD"]
  • placeholders => ["user.name.first", "account.balance"]

Values object: { user: { name: { first: 'Sam' } }, account: { balance: 12.34 } }

Resolution: look up each placeholder as a dot-path on the values object:

Zipped pieces: ["Hi, ", "Sam", " — balance: ", "12.34", " USD"]

Join => "Hi, Sam — balance: 12.34 USD"

Minimal API (example signatures)

  • parse(template: string) -> { statics: string[], placeholders: string[] }
  • render(parsed, values: (string | number | null | undefined)[], {strict?: boolean} = {}) -> string

Notes:

  • placeholders can be numeric indices or named keys depending on parsing syntax.
  • For named placeholders you may pass an object map instead of an array.

Usage (pseudo-JavaScript)

const template = ZipTemplate.parse("Hi, {{name}} — balance: {{balance}} USD");
const out = template.render({ name: "Sam", balance: 12.34 });
console.log(out); // "Hi, Sam — balance: 12.34 USD"

How this compares to common templating solutions

  • Handlebars / Mustache / Nunjucks / EJS

    • These are full-featured templating engines with logic (conditionals, loops), helpers, partials, escaping, and more.
    • ZipTemplates intentionally focuses only on interpolation. It does not provide logic, control flow, or template helpers.
    • Pros vs heavy engines: much smaller runtime, fewer allocations, simpler mental model, faster for plain interpolation.
    • Cons vs heavy engines: lacks features like HTML escaping, conditionals, partials, and custom helpers.
  • Native template literals (JS backticks) / string interpolation in other languages

    • Native templates are compiled into code at build time or used inline when source code contains the literal templates.
    • ZipTemplates is designed for templates that are specified at runtime (e.g., user-provided templates, templates from a database, or dynamically constructed templates).
    • Native templates are more ergonomic when templates are static and known at coding time; ZipTemplates shines when templates arrive or change at runtime.
  • Simple concatenation or join

    • Manual concatenation is straightforward but can become error-prone and allocate intermediate strings when building larger outputs.
    • ZipTemplates reduces allocations by preparing arrays and performing a single join at the end.
  • Performance and memory

    • ZipTemplates reduces the number of intermediate string concatenations, which can reduce GC pressure and improve throughput in hot paths that do many renders.
    • It’s best to benchmark in your environment. For simple interpolation-only templates, ZipTemplates will usually outperform heavier template engines because it does less work and allocates less.

Theoretical performance calculations

This section gives a compact, language-agnostic view of the costs involved when parsing and rendering with ZipTemplates. It focuses on asymptotic behavior, memory (allocations), and a small worked numeric example you can use to estimate cost for your templates.

  • $T$ — template length (characters)
  • $p$ — number of placeholders
  • $s$ — number of static segments ($s = p + 1$)
  • $S_{\mathrm{avg}}$ — average static segment length
  • $L_{\mathrm{avg}}$ — average length of resolved placeholder values
  • $O$ — total output length

Display formulas (LaTeX):

$$ O = s \cdot S_{\mathrm{avg}} + p \cdot L_{\mathrm{avg}} $$

$$ \mathrm{Time_{parse}} = O(T) $$

$$ \mathrm{Time_{render}} = O(p + O) $$

$$ \mathrm{Space_{aux}} = O(s + p) \quad\text{(plus final output } O(O)\text{)} $$

Parsing

  • Time: O(T). Parsing scans the template once to split it into statics and placeholders.
  • Memory: O(s + p) for the two arrays (number of entries). Each static string is usually a slice/substring of the template (language-dependent); if slicing copies, account for those allocations.

Rendering

  • Time: O(p + O). Resolving p placeholders (object lookups or index lookups) and then joining the pieces to produce O characters.
    • The cost to convert placeholder values to strings is proportional to the total length of those stringified values (roughly p * L_avg).
  • Memory / allocations:
    • One array of pieces of size s + p (or equivalent internal buffers) is created.
    • The final output string of length O must be allocated once by the runtime (join/concatenate step).
    • Per-placeholder temporary string allocations occur if values need coercion to string (depends on runtime/language).

Comparison to common alternatives (rough)

  • Repeated concatenation (e.g., building a string by incremental += or via many small concatenations): may cause many intermediate allocations depending on language and runtime. In the worst case, concatenating k parts naively can create O(k) intermediate buffers and lead to extra copying; cost can approach O(k * O) work in badly optimized implementations.
  • Heavy template engines: these typically parse into an AST (similar parse cost O(T)) but then execute node-by-node doing more work: condition evaluation, helper calls, escaping, and iteration. That extra work increases CPU cost and allocations proportional to feature usage.

Worked numeric example

  • Suppose a template with p = 10 placeholders, s = 11 statics. Let S_avg = 20 chars and L_avg = 8 chars.
    • Output length O = 1120 + 108 = 220 + 80 = 300 characters.
    • Parsing cost: O(T) (T might be around 300–400 chars depending on placeholder syntax).
    • Render cost: resolving 10 placeholders (small), constructing array of 21 pieces, and allocating final string of 300 chars.
    • Compared to naive repeated concatenation of 21 pieces, ZipTemplates performs a single final allocation for the joined result and a small array allocation; the naive approach may do several intermediate allocations depending on the runtime.

Practical benchmarking guidance

  • Benchmark with realistic templates and value shapes (short vs long substitutions, many vs few placeholders).
  • Measure both time and memory/GC allocations. In many languages you can track allocated bytes and number of GC cycles.
  • If templates are reused, measure the effect of caching parsed templates (avoid paying parse cost on each render).

Rules of thumb

  • For interpolation-only templates: rendering cost is dominated by final output size O and number of placeholders p; ZipTemplates minimizes intermediate allocations by using a single join/concatenate step.
  • If you need logic (loops/conditionals) or safe HTML escaping, compare the extra CPU/allocations of a full engine against the development and maintenance costs of implementing those features yourself.

Summary

  • Asymptotically, ZipTemplates is optimal for the interpolation-only use case: parse O(T), render O(p + O) time, and a small O(s+p) extra memory for arrays plus one O(O) allocation for the output string.

Fallback (plain text):

  • Output length: O = s _ S_avg + p _ L_avg
  • Parse time: Time_parse = O(T)
  • Render time: Time_render = O(p + O)
  • Aux space: Space_aux = O(s + p) + O(O) (final output)

Benchmarks

For small strings on Ryzen 8700GE

Benchmark Time ns (avg)
flatten json then zip_templates::render 360.13
already flat json zip_templates::render 36.787
zip_templates::render_from_vec_smart 21.823
tera::render 1090.5
mystical_runic::render 747.51
simple_replace 503.04
simple_replace_flat 181.57
     Running benches/zip_templates_bench.rs (target/release/deps/zip_templates_bench-4b3e190c814a6892)
Gnuplot not found, using plotters backend
zip_templates::render   time:   [359.53 ns 360.13 ns 360.85 ns]
                        change: [−4.1652% −2.7557% −1.0608%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 12 outliers among 100 measurements (12.00%)
  1 (1.00%) high mild
  11 (11.00%) high severe

zip_templates::render_flat
                        time:   [36.707 ns 36.787 ns 36.880 ns]
                        change: [−4.7696% −4.3873% −4.0016%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 11 outliers among 100 measurements (11.00%)
  8 (8.00%) high mild
  3 (3.00%) high severe

zip_templates::render_from_vec_smart
                        time:   [21.784 ns 21.823 ns 21.863 ns]
                        change: [−8.7911% −8.4338% −8.0677%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 9 outliers among 100 measurements (9.00%)
  6 (6.00%) high mild
  3 (3.00%) high severe

tera::render            time:   [1.0891 µs 1.0905 µs 1.0921 µs]
                        change: [−4.8755% −4.4775% −4.0901%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 11 outliers among 100 measurements (11.00%)
  6 (6.00%) high mild
  5 (5.00%) high severe

mystical_runic::render  time:   [744.88 ns 747.51 ns 750.97 ns]
Found 9 outliers among 100 measurements (9.00%)
  2 (2.00%) high mild
  7 (7.00%) high severe

simple_replace          time:   [502.19 ns 503.04 ns 504.08 ns]
                        change: [−1.6621% −1.2387% −0.8054%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 11 outliers among 100 measurements (11.00%)
  4 (4.00%) high mild
  7 (7.00%) high severe

simple_replace_flat     time:   [181.32 ns 181.57 ns 181.86 ns]
                        change: [+12.015% +12.434% +12.882%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 8 outliers among 100 measurements (8.00%)
  4 (4.00%) high mild
  4 (4.00%) high severe

Trade-offs and when to use

Use ZipTemplates when:

  • You only need interpolation (no loops/conditionals/helpers).
  • Templates are provided or changed at runtime.
  • You care about minimizing allocations and maximizing rendering throughput.

Avoid ZipTemplates when:

  • You need escaping, conditionals, loops, or template composition across files.
  • You need a mature ecosystem of helpers and tooling (internationalization, partials, etc.).

Edge cases and considerations

  • Missing placeholders: decide between empty string substitution vs. throwing an error (provide strict mode).
  • Escaping: ZipTemplates does not escape HTML or other contexts by default — callers must escape values where needed.
  • Large templates: parsing and storing arrays uses memory proportional to template structure — still usually less allocation-heavy than repeated concatenation.
  • Placeholder collision / ambiguous syntax: pick a clear placeholder syntax and document it.

Extensibility

Possible small additions that remain lightweight:

  • Strict mode: throw on missing values.
  • Named placeholders: support object maps for rendering.
  • Caching parsed templates: if templates are reused, store parsed results to avoid reparsing.

Quick checklist for implementers

  • Choose placeholder syntax and document it.
  • Implement parse and render with clear semantics for missing values.
  • Add an optional escape hook for common contexts (HTML, URI).
  • Add tests: happy path and missing placeholder behavior.

Files changed / created

  • README.md — this file: describes the project, usage, and comparisons.

Final notes

ZipTemplates is intentionally small and focused. If you need richer templating features, pair ZipTemplates with a small set of utilities (escaping, caching, and a strict mode) or switch to a full template engine when complexity demands it.

Commit count: 0

cargo fmt