Expand description
macro-template is a procedural macro that generates repeated Rust code in multiple places with table-driven inputs.
§Motivation
This crate is inspired by match-template and macro_find_and_replace.
When developing ScopeDB, we introduced these two proc-macros to generate repeated code for match arms and impls. I noticed that they share a common pattern: iterating over a table of values and generating code based on it. I wanted to unify these patterns into a single, concise, but flexible macro that can handle various use cases. That’s how macro-template was born.
Last but not least, I found seq-macro and borrowed some ideas from it, such as iterating over a range of numbers, characters, or bytes, and using a syntax #( ... )* to generate partial repeated substitutions. This eliminates the need for an extra template_match! to handle repetitions in match arms, and allows for more flexible code generation.
Go back to the beginning, why do you need macro_template::template! at all? Isn’t it the same as a simple macro_rules!?
macro_rules! impl_serialize {
($($ty:ty),* $(,)?) => {
$(
impl serde_core::Serialize for BSize<$ty> {
fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where
S: serde_core::Serializer,
{
if ser.is_human_readable() {
ser.collect_str(self)
} else {
self.0.serialize(ser)
}
}
}
)*
};
}
impl_serialize!(u8, u16, u32, u64, usize);Except that macro_template::template! supports more flexible substitution patterns as shown in the Examples section, template! has a concise syntax, and it saves you from declaring an extra macro_rules!, naming it, and invoking it.
The example above can be rewritten as:
macro_template::template! {
for Ty in [u8, u16, u32, u64, usize] {
impl serde_core::Serialize for BSize<Ty> {
fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where
S: serde_core::Serializer,
{
if ser.is_human_readable() {
ser.collect_str(self)
} else {
self.0.serialize(ser)
}
}
}
}
}§Examples
First, you can generate code with a template and a matrix of values:
macro_template::template! {
for (Endian, Method) in [
(LittleEndian, to_le_bytes),
(BigEndian, to_be_bytes),
(NativeEndian, to_ne_bytes)
],
for (Ty, Width) in [
(u16, 2),
(u32, 4),
],
{
impl StoreBytes<Endian, Width> for Ty {
fn store_bytes(&self) -> [u8; Width] {
self.Method()
}
}
}
}Without splice syntax, the whole template body is repeated once for each row. You can also do substitutions only partially with #( ... )*. Like quote, a single separator token tree can be written before *, such as #( ... ),*. For example, to generate match arms:
macro_template::template! {
for T in [Int, Real, Double] {
match Foo {
#( EvalType::T => { panic!("{}", EvalType::T); } ),*,
EvalType::Other => unreachable!(),
}
}
}When a template contains #( ... )* or #( ... ),*, template variables are substituted only inside the splice body, and 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.
Naturally, if the match arm differs left-hand side and right-hand side:
macro_template::template! {
for (K, Value) in [
(Apple, "apple"),
(Banana, "banana"),
(Cherry, "cherry"),
] {
match kind {
#( Kind::K => { println!("{}", Value); } ),*
}
}
}Inputs can also be ranges of numbers, characters, or bytes. Range inputs are written directly after in, without surrounding brackets:
// sequential numeric counter
let tuple = (1000, 100, 10);
let mut sum = 0;
macro_template::template! {
for i in 0..3 {
sum += tuple.i;
}
}
assert_eq!(sum, 1110);
// sequential character collector
let mut string = String::new();
macro_template::template! {
for c in 'x'..='z' {
string.push(c);
}
}
assert_eq!(string, "xyz");Integer ranges preserve the radix, suffix, and shared padding width from their bounds.
You can combine multiple iterators in a single template:
let mut values = vec![];
macro_template::template! {
for Prefix in ["read", "write"],
for Code in 200..=201 {
values.push((Prefix, Code));
}
}
assert_eq!(
values,
[("read", 200), ("read", 201), ("write", 200), ("write", 201)],
);Multiple input clauses form a Cartesian product in clause order.
Macros§
- template
- Generates repeated Rust code from one or more table-driven input clauses.