Rust Counting with Macros
Counting with macros means hiding a count inside the code a macro writes. A macro cannot count directly while it expands.
A helper that throws its input away
This helper takes any token plus a stand-in value, and yields only the stand-in:
macro_rules! replace_with_unit {
($_t:tt $sub:expr) => { $sub };
}Turn tokens into units, then measure the array
One () per token goes into an array literal, and .len() does the counting:
($($t:tt)*) => {
<[()]>::len(&[$(replace_with_unit!($t ()),)*])
};The simple adding way — fine for a few tokens
Each token becomes a + 1. Short inputs only: the expression tree grows as deep as the input is long:
($($tts:tt)*) => { 0usize $(+ replace_with_unit!($tts 1usize))* };Example
macro_rules! replace_with_unit {
($_t:tt $sub:expr) => { $sub };
}
macro_rules! how_many {
($($t:tt)*) => {
<[()]>::len(&[$(replace_with_unit!($t ()),)*])
};
}
fn main() {
println!("words = {}", how_many!(one two three four));
println!("none = {}", how_many!());
}Key points
- A macro cannot count directly
- Expansion produces code
- It does not run anything
- So the count must be encoded in the generated code
- Replacement trick: swap every captured token for the same fixed value, then measure how many copies there are
- Slice-length trick: replace each token with a unit value ()
- Build the array [(), (), ()] and read its length
- The count becomes an ordinary .len() call
- The adding trick, 0 + 1 + 1 + 1, also works for small inputs
- It builds one deep expression tree
- That tree can overwhelm the compiler on long inputs
- Recursive counting — one call per token — hits the macro recursion limit quickly, so it suits only short inputs
In this example, how_many captures its whole input as tokens. The expansion swaps each token for the unit value (), using replace_with_unit. It places the results in an array. Four tokens become [(), (), (), ()]. The generated code then asks for that array's length, which is the count. main prints words = 4 for the four words. The second call passes nothing: the array is empty, its length is 0, and the program prints none = 0. The counting happened in the shape of the generated code — no counter ever existed.
Note
The slice-length version is the one real crates use
Its generated code stays shallow, so it keeps working for thousands of tokens
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
In the slice-length counting trick, what is actually counted?