Expand description
quote! with control flow.
§template-quote
A drop-in quote! replacement that adds
template-engine-like control flow — write if, for, while and let
directly inside the macro instead of assembling TokenStreams by hand.
use template_quote::quote;
use proc_macro2::{Ident, Span};
let fields: Vec<Ident> = ["x", "y", "z"]
.iter()
.map(|n| Ident::new(n, Span::call_site()))
.collect();
let derive_debug = true;
let tokens = quote! {
#(if derive_debug) { #[derive(Debug)] }
struct Point {
#(for f in &fields) { #f: i32, }
}
};
// #[derive(Debug)] struct Point { x: i32, y: i32, z: i32, }§Why template-quote?
- Backward-compatible with
quote!— every existingquote!template works unchanged, so adopting it is zero-cost. - Inline control flow —
#(if …),#(else),#(for x in …),#(while …)and#(let … = …)branch and loop inside the quotation. - Nested repetition that classic
quote!rejects, e.g.#(#(#a #b)*)*. - Inline expressions & statements —
#{ expr }splices a computed value;#{ stmt; }runs code mid-expansion. quote_spanned!/quote_configured!for span control and custom crate paths.
§Install
cargo add template-quote proc-macro2The generated code refers to proc_macro2, so
add it alongside template-quote. MSRV is Rust 1.56.
§How it compares
- vs
quote: a superset — everyquote!template works unchanged, plus inlineif/for/while/letand nested repetition. - vs
proc-quote: the inspiration for this crate;template-quotekeeps fullquote!syntax compatibility.
§Interpolation
The original quote! macro syntax is fully supported. See quote docs.
For backward compatibility, interpolation rules are the same as in the traditional quote! macro. Interpolation uses #var (similar to $var in macro_rules!). Most values from syn are interpolated via the ToTokens trait.
§Rules
Repetition uses syntax like #(...)* or #(...),*, repeating over the variables (#var) inside the pattern. A repeated #var may be any Iterator, a slice / array / Vec (iterated by reference, not consumed), or a single ToTokens value (repeated unchanged on every iteration). At least one #var in the group must be a real iterator to bound the repetition.
#(...)*repeats...with no separator. At least one variable must appear in....#(...),*does the same, but inserts,as a separator.
§Problem
Classic interpolation is limited, so this crate introduces new template syntax. For example, the following code is not allowed because #var1 cannot be nested in this way:
let var1 = vec!['a', 'b'];
let var2 = vec![vec![1, 2], vec![3, 4]];
let tokens = quote!{
#(#(#var1 #var2)*)*
};
assert_eq!("'a' 1i32 'a' 2i32 'b' 3i32 'b' 4i32", tokens.to_string());§Template syntax
Template syntax is procedural-like and lets you use structured statements inside the macro.
§If syntax
This code iterates over #i (via interpolation) and emits i32 into the TokenStream when the value meets the condition.
let i = vec![1, 2, 3];
let tokens = quote!{
#(
#(if i > &2) {
#i
}
)*
};
assert_eq!("3i32", tokens.to_string());if-else and if-else-if are also supported.
let i = vec![1, 2, 3];
let tokens = quote!{
#(
#(if i > &2) {
+ #i
}
#(else) {
- #i
}
)*
};
assert_eq!("- 1i32 - 2i32 + 3i32", tokens.to_string());let i = vec![1, 2, 3, 4, 5];
let tokens = quote!{
#(
#(if i % &2 == 0) {
+ #i
}
#(else if i % &3 == 0) {
- #i
}
#(else) {
#i
}
)*
};
assert_eq!("1i32 + 2i32 - 3i32 + 4i32 5i32", tokens.to_string());§For syntax
for syntax iterates over variables (similar to interpolation), but lets you explicitly choose which variable to iterate.
let v1 = vec![1, 2];
let v2 = vec!['a', 'b'];
let tokens = quote!{
#(for i1 in &v1) {
#(for i2 in &v2) {
#i1 -> #i2
}
}
};
assert_eq!("1i32 -> 'a' 1i32 -> 'b' 2i32 -> 'a' 2i32 -> 'b'", tokens.to_string());The inner loop can be replaced with interpolation:
let v1 = vec![1, 2];
let v2 = vec!['a', 'b'];
let tokens = quote!{
#(for i1 in &v1) {
#(
#i1 -> #v2
)*
}
};
assert_eq!("1i32 -> 'a' 1i32 -> 'b' 2i32 -> 'a' 2i32 -> 'b'", tokens.to_string());You can also specify a separator with a for statement.
let v = vec![1, 2];
let tokens = quote!{
#(for i in v) | { #i }
};
assert_eq!("1i32 | 2i32", tokens.to_string());Interpolation cannot use variables bound by for syntax directly. For example:
let v = vec![vec![1, 2], vec![3]];
let tokens = quote!{
#(
#(for i in v) { #i }
),*
};
assert_eq!("1i32 2i32 , 3i32", tokens.to_string());This fails because no interpolation variable is available:
error: Repetition group `#( # ( .. ) { .. } )*` contains no interpolation variable (`#var`)
--> src/lib.rs
|
| #(for i in v) { #i }
| ^^^^^^^^^^^^^^^^^^^^
|
= note: a repetition needs at least one `#var` inside it to drive the iterationIn this case, use #(for i in #v) to specify which variable to iterate via interpolation:
let v = vec![vec![1, 2], vec![3]];
let tokens = quote!{
#(
#(for i in #v) { #i }
),*
};
assert_eq!("1i32 2i32 , 3i32", tokens.to_string());§While syntax
let mut v = vec![1, 2].into_iter();
let tokens = quote!{
#(while v.next().is_some()) { hello }
};
assert_eq!("hello hello", tokens.to_string());§While-let syntax
let mut v = vec![1, 2].into_iter();
let tokens = quote!{
#(while let Some(i) = v.next()) { #i }
};
assert_eq!("1i32 2i32", tokens.to_string());As with for syntax, variables bound in while are not iterable through interpolation. For example:
let mut v = vec![1, 2].into_iter();
quote!{
#(
#(while let Some(i) = v.next()) { #i }
)*
};This fails.
§Let syntax
let syntax binds new variables that can be used inside the block.
let v = vec![(1, 'a'), (2, 'b')];
let tokens = quote!{
#(for i in v), {
#(let (n, c) = i) {
#n -> #c
}
}
};
assert_eq!("1i32 -> 'a' , 2i32 -> 'b'", tokens.to_string());Here, #n and #c are not iterable via interpolation.
§Inline expression
You can place inline expressions in quote!.
let v = vec![1, 2];
let tokens = quote!{
#(for i in v){
#i -> #{ i.to_string() }
}
};
assert_eq!("1i32 -> \"1\" 2i32 -> \"2\"", tokens.to_string());The following example fails because the macro cannot determine which variable should be iterated:
let v = vec![1, 2];
let tokens = quote!{
#(
#{ v.to_string() }
)*
};
assert_eq!("\"1\" \"2\"", tokens.to_string());In this case, use #i inside the inline expression to specify the interpolation variable:
let v = vec![1, 2];
let tokens = quote!{
#(
#{ #v.to_string() }
)*
};
assert_eq!("\"1\" \"2\"", tokens.to_string());§Inline statement
You can place arbitrary statements inside the macro. For example:
let v = vec![1, 2, 3];
let tokens = quote!{
#(
#v
#{ eprintln!("debug: {}", &v); }
)*
};
assert_eq!("1i32 2i32 3i32", tokens.to_string());This prints:
debug: 1
debug: 2
debug: 3To avoid ambiguity, all inline statements must end with ;. For example, an if statement in inline-statement syntax needs an extra ;:
let v = vec![1, 2, 3];
quote!{
#(
#v
#{ if v >= &2 { eprintln!("debug: {}", &v); } ; }
)*
};§Break, Continue
You can use control-flow statements like break and continue in inline statements, but this can be risky.
If you use break; inside a group (like { ... } or ( ... )), it aborts emission of the whole group, and nothing is emitted for that group. For example, the following code emits only one token:
let v = vec![1, 2, 3];
let tokens = quote!{
#(for i in v) {
#i // emitted once
// This block is not emitted
{
#i
#{ break; }
}
}
};
assert_eq!("1i32", tokens.to_string());break also affects interpolation syntax:
let v = vec![1, 2, 3];
let tokens = quote!{
#(
#v
#{ break; }
),*
};
assert_eq!("1i32", tokens.to_string());break can even escape outside the quote! macro. In this example, the internal break affects the outer for loop:
let mut v = Vec::new();
for _ in 0..3 {
let tokens = quote!{
#{ break; }
};
v.push(tokens);
}
assert_eq!(v.len(), 0);§Spanned and configured output
Besides quote!, two sibling macros are provided:
quote_spanned!works likequote!but applies an explicit span to every emitted token:quote_spanned! { my_span => /* ... */ }.quote_configured!overrides the crate paths and default span that the generated code refers to (useful whenproc_macro2or this crate are re-exported under non-standard paths). A leading{ key: value, ... } =>block configures it; see its documentation for the accepted keys.
§Using this crate
This crate is useful for proc-macro development. A typical proc-macro crate using template_quote has the following Cargo.toml:
[package]
name = "your_crate_name"
version = "0.0.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
template-quote = "0.5"
proc-macro2 = "1.0"And the following src/lib.rs:
extern crate proc_macro;
extern crate proc_macro2;
extern crate template_quote;
use template_quote::quote;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
#[proc_macro]
pub fn my_macro(_: TokenStream) -> TokenStream {
quote! { /* something here */ }.into()
}Then you can use it like this:
extern crate your_crate_name;
use your_crate_name::my_macro;
my_macro!()Macros§
- quote
- See module-level doc.
- quote_
configured quote_configured!is a configurable version ofquote!. A leading{ key: value, ... } =>block overrides the paths and span the generated code refers to. Every key is optional; the accepted keys and their defaults are:- quote_
spanned quote_spanned!emits tokens using the span given before=>.
Traits§
- ToTokens
- Types that can be interpolated inside a
quote!invocation.