Expand description
macro-template provides template!, a procedural macro for generating repeated Rust code from compact, table-driven inputs.
§Motivation
macro-template resulted from a ScopeDB code refactor. ScopeDB has used match-template for variant/type match arms and macro_find_and_replace for repeating Rust fragments over type lists. While replacing them, I found that I wanted the same thing in both places: write the choices once, name the columns, and use those names in Rust syntax. There was no existing macro fitting that shape.
That is the table-driven case template! is built around:
trait ReadLe {
fn read_le(input: &[u8]) -> Self;
}
macro_template::template! {
for (Ty, Width) in [
(u16, 2),
(u32, 4),
(u64, 8),
] {
impl ReadLe for Ty {
fn read_le(input: &[u8]) -> Self {
Ty::from_le_bytes(input[..Width].try_into().unwrap())
}
}
}
}
assert_eq!(u16::read_le(&[0x34, 0x12]), 0x1234);
assert_eq!(u32::read_le(&[1, 0, 0, 0]), 1);When looking for existing approaches, I also found seq-macro, which covers a neighboring repetition pattern: range-driven generation, where N in 0..=2 becomes literal tokens like 0, 1, and 2. template! keeps both forms under one syntax: table rows, ranges, #( ... )* for partial repetition, and multiple for clauses for Cartesian products. The examples below expand each case.
§Examples
§Whole-body repetition
Without splice syntax, the whole template body is repeated once per input row:
trait TypeName {
const NAME: &'static str;
}
macro_template::template! {
for (Ty, Name) in [
(u8, "u8"),
(u16, "u16"),
(u32, "u32"),
] {
impl TypeName for Ty {
const NAME: &'static str = Name;
}
}
}
assert_eq!(<u16 as TypeName>::NAME, "u16");§Partial repetition
When only part of a surrounding construct should repeat, put that part in #( ... )*. A single separator token tree can be written before *, such as #( ... ),* for comma-separated output:
fn keyword_code(text: &str) -> Option<u8> {
macro_template::template! {
for (Pat, Code) in [
("async", 1u8),
("await", 2u8),
] {
match text {
#(Pat => Some(Code)),*,
_ => None,
}
}
}
}
assert_eq!(keyword_code("async"), Some(1));
assert_eq!(keyword_code("await"), Some(2));
assert_eq!(keyword_code("fn"), None);When a template contains #( ... )* or #( ... ),*, template variables are substituted only inside the splice body, and the surrounding tokens are emitted once. Surrounding identifiers stay literal, even when they have the same name as a template variable. If a value should vary, place it in the splice body.
#( ..., )* and #( ... ),* are different: the latter does not produce a trailing comma. This matches delimiter repetition in macro_rules!.
§Range inputs
Inputs can also be ranges of integers, characters, or bytes. Range inputs are written directly after in, without surrounding brackets. Wrap the range in parentheses when calling a range method such as .rev():
let tuple = ("red", "green", "blue");
let mut fields = vec![];
macro_template::template! {
for N in (0..3).rev() {
fields.push(tuple.N);
}
}
assert_eq!(fields, vec!["blue", "green", "red"]);This cannot be written using an ordinary for-loop because elements of a tuple can only be accessed by their integer literal index, not by a variable.
Integer ranges preserve the radix, suffix, and shared padding width from their bounds. .strip_prefix() removes the radix prefix before substitution, which is useful when combining range values with paste for identifier generation:
macro_template::template! {
for N in (0x00A..=0x00C).strip_prefix() {
paste::paste! {
enum Pin {
#( [<Pin N>], )*
}
}
}
}
let _ = (Pin::Pin00A, Pin::Pin00B, Pin::Pin00C);§Cartesian products
Multiple input clauses form a Cartesian product in clause order. This is useful when two or more independent dimensions share the same generated body:
struct Cpu;
struct Gpu;
trait Kernel<T> {
fn run(input: T) -> T;
}
macro_template::template! {
for Backend in [Cpu, Gpu],
for Ty in [f32, f64],
{
impl Kernel<Ty> for Backend {
fn run(input: Ty) -> Ty {
input
}
}
}
}
assert_eq!(<Gpu as Kernel<f64>>::run(1.5), 1.5);Macros§
- template
- Generates repeated Rust code from one or more table-driven input clauses.