#compile-time #macro #sql #composition

macro tomplate-macros

Procedural macros for the tomplate TOML-based template composition library

1 unstable release

Uses new Rust 2024

0.1.0 Sep 15, 2025

#43 in #composition


Used in tomplate

MIT/Apache

70KB
1K SLoC

Tomplate Procedural Macros

This crate provides the procedural macros that power Tomplate's compile-time template processing. These macros expand at compile time to produce static strings, ensuring zero runtime overhead for template processing.

Macros Provided

tomplate! - Main Template Macro

The tomplate! macro is the primary interface for template processing. It supports two distinct modes of operation:

Mode 1: Direct Template Invocation

Process a single template with parameters:

// File-based template from registry
const QUERY: &str = tomplate!("user_query", 
    fields = "id, name",
    condition = "active = true"
);

// Inline template (when not found in registry)
const GREETING: &str = tomplate!("Hello {name}!", 
    name = "World"
);

Mode 2: Composition Block

Define multiple templates with local variables:

tomplate! {
    // Local variables (not exported)
    let common_fields = tomplate!("id, name, email");
    let active_filter = tomplate!("status = 'active'");
    
    // Exported constants (available outside block)
    const USER_QUERY = tomplate!(
        "SELECT {fields} FROM users WHERE {filter}",
        fields = common_fields,
        filter = active_filter
    );
    
    const COUNT_QUERY = tomplate!(
        "SELECT COUNT(*) FROM users WHERE {filter}",
        filter = active_filter
    );
}

tomplate_eager! - Eager Macro Expansion

Eagerly expands nested tomplate! and concat! macros before passing to outer macros:

// Problem: sqlx expects a string literal
// This won't work:
// sqlx::query!(tomplate!("select_user", id = "5"))

// Solution: Use tomplate_eager!
tomplate_eager! {
    sqlx::query!(tomplate!("select_user", id = "5"))
        .fetch_one(&pool)
        .await?
}

How Template Resolution Works

The tomplate! macro uses a two-step resolution process:

  1. Registry Lookup: First checks if the string matches a template name in the amalgamated template registry (created by tomplate-build at build time)

  2. Inline Fallback: If not found in registry, treats the string itself as an inline template with the simple engine

This allows seamless mixing of pre-defined and ad-hoc templates:

// If "header" exists in registry, uses that template
const HEADER: &str = tomplate!("header", title = "My App");

// If "Welcome {user}!" doesn't exist in registry, uses it as inline template
const WELCOME: &str = tomplate!("Welcome {user}!", user = "Alice");

Parameter Types

Templates accept various parameter types:

  • String literals: "value"
  • Numbers: 42, 3.14
  • Booleans: true, false
  • Nested templates: tomplate!("other_template", ...)
const EXAMPLE: &str = tomplate!("template_name",
    text = "Hello",
    count = 42,
    pi = 3.14,
    enabled = true,
    nested = tomplate!("inner", value = "data")
);

Template Engines

Templates can use different engines based on the engine field in TOML:

  • simple (default): Basic {variable} substitution
  • handlebars: Full Handlebars with conditionals, loops, helpers
  • tera: Jinja2-like with filters and control structures
  • minijinja: Lightweight Jinja2 implementation

The engine is determined at build time from the template definition.

Compile-Time Processing

All template processing happens at compile time:

  1. Build script discovers and amalgamates templates
  2. Macro reads amalgamated templates at compile time
  3. Templates are processed and expanded to string literals
  4. Final binary contains only static strings

This ensures zero runtime overhead and compile-time validation of templates.

Dependencies

~1–5.5MB
~106K SLoC