RustArena

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

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:

  1. at least 3 bytes long, else "too short"
  2. at most 16 bytes long, else "too long"
  3. only ASCII letters, digits and _, else "only letters, digits and _ allowed"
  4. doesn't start with a digit, else "must not start with a digit"

Previous: Your first macro_rules! macro Next: Repetition