Skip to content

← Macros, FFI and Type-Driven Design step 12 of 28

Hard Primitives

Follow-set rules: why the compiler rejects your matcher

Repair four macro matchers so the supplied all() function compiles and returns [3, 30, 1, 0, 1, 0, 6, 15].

pub fn all() -> Vec<i64>

Keep all(), its eight macro calls and the macro transcribers unchanged. Change only the matchers, the patterns before => in each macro definition.

Calls in all() Required results
add_pair!(1 + 2), add_pair!(10 + 20) 3, 30
type_size!(u32 bytes 4), type_size!(u8 bytes 4) 1, 0
matches_either!(2, 1 | 2), matches_either!(5, 1 | 2) 1, 0
run_then!(let z = 3; then z * 2), run_then!(let q = 10; then q + 5) 6, 15

type_size reports whether the byte size equals the supplied number. matches_either reports whether the value matches either pattern. Both use 1 for true and 0 for false.

Why these definitions are rejected

A fragment such as $a:expr captures a Rust expression. Rust restricts which tokens may follow certain fragment kinds in a matcher. These follow-set rules leave room for future syntax without changing the meaning of existing macros. A matcher can violate them even if its intended calls look clear.

The rules are checked at macro definition time. For example, $a:expr + is rejected because + is not an allowed follower for an expression fragment. The problem is in the matcher, before expansion of any particular call.

Repair each boundary

add_pair: The supplied operands are literals. Use a fragment kind that matches those operands and permits the following +, such as literal. This repairs these calls; it does not turn the macro into a parser for arbitrary unparenthesized expressions.

type_size: $t:ty bytes is invalid because bytes cannot follow a ty fragment. The supplied types u32 and u8 are single tokens, so tt can capture them. One tt matches one token or one delimited token tree; it does not automatically capture an ungrouped type such as Vec<u8>.

matches_either: In the edition used here, pat includes top-level alternatives, so a literal | cannot follow $p:pat. Use pat_param to capture an individual alternative before the separator. The macro definition's edition controls this rule; before edition 2021, pat had the narrower form.

run_then: A stmt fragment cannot be followed directly by then, but it can be followed by ;. For the supplied let statements, the fragment does not consume the trailing semicolon. Match that semicolon explicitly before then.

Do not add a semicolon after $s in the transcriber. The captured statement is reinserted as a parsed statement there, and the supplied expansion already works. An extra semicolon triggers redundant_semicolons for these calls. The general rule has an exception: item statements that require semicolons, such as use items, include them in a stmt fragment.

A legal expression separator

This complete example uses ; to separate two expression fragments:

macro_rules! add_values {
    ($left:expr; $right:expr) => {
        $left + $right
    };
}

fn main() {
    let x = 4;
    assert_eq!(add_values!(x * 2; 3), 11);
}

A comma or => can also follow an expr or stmt fragment. Other kinds have their own rules: literal and tt have no follow-set restriction, while vis does. An unrestricted follow set still does not guarantee that a matcher is unambiguous or that its expansion is valid Rust.

Your solution must preserve the supplied calls, produce all eight results and pass Clippy with warnings treated as errors.