1 unstable release
Uses new Rust 2024
| 0.1.0 | Sep 15, 2025 |
|---|
#43 in #composition
Used in tomplate
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:
-
Registry Lookup: First checks if the string matches a template name in the amalgamated template registry (created by
tomplate-buildat build time) -
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:
- Build script discovers and amalgamates templates
- Macro reads amalgamated templates at compile time
- Templates are processed and expanded to string literals
- Final binary contains only static strings
This ensures zero runtime overhead and compile-time validation of templates.
Dependencies
~1–5.5MB
~106K SLoC