Step 2 of 6
Fragment specifiers and generating code
expr is one of several fragment specifiers. Each tells the macro what kind of syntax to match:
| Specifier | Matches | Examples |
|---|---|---|
expr |
an expression | 2 + 3, x.len(), "hi" |
ident |
a name | max_players, Meters |
ty |
a type | u32, Vec<String>, &'static str |
literal |
a literal | 8, "hi", 'c', true |
pat |
a pattern | Some(x), 1..=5, _ |
block |
a { ... } block |
{ let a = 1; a + 1 } |
stmt |
a statement | let x = 5 |
tt |
one token tree: a single token, or anything inside matching (), [] or {} |
+, foo, (a, b) |
item, path, lifetime, vis, meta |
an item, a path, a lifetime, a visibility (pub), an attribute's contents |
fn f() {}, std::io, 'a, pub |
With ident and ty, a macro can generate items: functions, structs, impls. That's where macros save the most typing.
macro_rules! constant_fn {
($name:ident -> $t:ty = $value:expr) => {
fn $name() -> $t {
$value
}
};
}
macro_rules! newtype {
($name:ident($inner:ty)) => {
#[derive(Debug, Clone, Copy, PartialEq)]
struct $name($inner);
};
}
constant_fn!(max_players -> u32 = 8);
constant_fn!(server_name -> &'static str = "arena-eu-1");
newtype!(Meters(f64));
fn main() {
println!("{} players on {}", max_players(), server_name());
let d = Meters(12.5);
println!("{:?} is {} m", d, d.0);
}
8 players on arena-eu-1
Meters(12.5) is 12.5 m
- The matcher
$name:ident -> $t:ty = $value:exprinvents its own little syntax.->and=are plain tokens the call must contain. fn $name() -> $tpastes the identifier and the type into a function definition. The macro calls sit at the top level of the file, so they expand to items there.newtype!(Meters(f64))expands to a whole struct with its derives. The standard library uses this trick to implement traits for all twelve integer types with one macro.
Common mistakes
What may follow a fragment is restricted. So that future Rust syntax can't change how a macro parses, an expr or stmt may only be followed by =>, , or ;. Types and paths allow a few more (=, |, {, :, >, as, where).
macro_rules! both {
($a:expr and $b:expr) => {
$a && $b
};
}
fn main() {
println!("{}", both!(true and false));
}
error: `$a:expr` is followed by `and`, which is not allowed for `expr` fragments
Use a comma (($a:expr, $b:expr)) or match $a:tt and $b:tt if each side is a single token.
Using the wrong specifier. An ident is only a name: passing x + 1 where the macro expects $v:ident fails with no rules expected and points at the +.
Your task
Write an id_type! macro that generates an id newtype. id_type!(UserId, u32) should expand to a tuple struct UserId(u32) deriving Debug, Clone, Copy, PartialEq, plus an impl with fn new(value: u32) -> Self and fn value(&self) -> u32. Then use it twice: UserId wrapping u32 and Sku wrapping u64.
Challenge 2
Write make_adder!(name, amount), which generates a function fn name(x: i32) -> i32 returning x + amount. Then use it three times to create add_five (adds 5), add_ten (adds 10) and subtract_one (adds -1).
Challenge 3
Because a macro expands inside the function that calls it, it can return from that function. That's how anyhow::ensure! works. Write ensure!(condition, message): if the condition is false, it returns Err(message.to_string()) from the calling function.
Then use it (at least three times) in validate_username, with these checks in this order:
- at least 3 bytes long, else
"too short" - at most 16 bytes long, else
"too long" - only ASCII letters, digits and
_, else"only letters, digits and _ allowed" - doesn't start with a digit, else
"must not start with a digit"