Skip to content

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

Medium Primitives

Build vector, pattern-matching and map macros

Finish three macro_rules! definitions, then use the supplied run function to check them. my_vec! and my_matches! practice forms of the standard vec! and matches! macros. hashmap! is a custom map literal macro; the standard library does not provide a macro with that name.

pub fn run(xs: Vec<i64>) -> (Vec<i64>, Vec<bool>, Vec<(String, i64)>)

Keep the supplied driver and counting helpers. Complete the macro arms rather than replacing their uses with ordinary expressions.

Required forms

Macro Forms to support
my_vec! Empty, a comma-separated list with an optional trailing comma, and my_vec![element; count].
my_matches! An expression and pattern, optionally followed by if guard. Top-level or-patterns must work.
hashmap! Empty, or one or more key => value pairs with an optional trailing comma.

These are exercise implementations, not exact copies of every standard-library detail. In particular, this my_matches! contract does not require a trailing comma after the pattern or guard.

What run returns

The tuple contains three collections:

  • nums: [1, 2, 3, 0, 0] followed by all input values.
  • flags: four booleans about the first input value, using 0 for empty input. In order, they test equality to zero, membership in 1..=9, negativity, and membership outside 0..=9.
  • pairs: sorted entries for alpha = 1, beta = 2, empty_len = 0 and gamma = sum(xs). The input keeps intermediate sums within i64.

For xs = [-4, 10], the flags are [false, false, true, true] because all four tests examine -4. The second input contributes to the sum, which is 6. The final flag is an or-pattern, not another guard.

Build the vector forms

The empty arm already returns Vec::new(). Add $(,)? to the list matcher to allow one trailing comma. Its existing expansion is:

<[_]>::into_vec(::std::boxed::Box::new([$($x),+]))

This macro fragment constructs an array from the expressions and converts its boxed slice into a vector. The conversion reuses the box's allocation. Do not infer a fixed total allocation count: element expressions can allocate, and zero-sized element types do not need a vector buffer allocation.

For the repeat arm, evaluate the element and count once, then build a vector containing that many copies of the value. std::iter::repeat_n(element, count) followed by collect::<Vec<_>>() is one approach and requires Clone. The element expression is still evaluated when the count is zero. Cloning shared handles can preserve sharing; it does not necessarily make independent copies of the underlying data. This exercise does not require matching the standard macro's internal implementation or exact number of clone calls.

Add the optional match guard

The matcher can capture the guard as an optional group:

($e:expr, $p:pat $(if $g:expr)?) => {
    match $e {
        $p $(if $g)? => true,
        _ => false,
    }
};

This is a macro arm, not a standalone Rust item. The same optional group appears in the expansion, between the pattern and =>. If the pattern matches, its guard decides whether that arm applies. The fallback returns false. The expression being matched is evaluated once.

if is permitted after a pat fragment. In macros defined in edition 2021 or later, pat also accepts top-level or-patterns such as 1 | 2. The edition of the macro definition controls this behavior; this exercise uses a modern edition. There is no need for a separate or-pattern arm.

Count map entries without evaluating them

The empty arm already returns HashMap::new(). For the nonempty arm, reserve capacity for the number of supplied pairs, then insert them in source order:

let mut m = ::std::collections::HashMap::with_capacity(count_tts!($($k),*));
$( m.insert($k, $v); )+
m

This is the body of a macro expansion. Put it inside a block so it can be used as an expression. with_capacity reserves room for at least the requested number of entries; it need not allocate an exactly sized table. Repeated equal keys replace the earlier value, so the final length may be smaller than the pair count. A map's iteration order is unspecified, which is why the driver sorts its output.

count_tts! replaces each forwarded key expression with () and counts the resulting array. It does not evaluate those expressions. Each key and value should be evaluated only by its insertion. The helper's tt matcher can receive an expression forwarded by another macro as one fragment; it is not a general parser for arbitrary ungrouped expressions typed directly into it.

Keep the separate empty arm. It avoids generating a mutable local with no insertions, which can trigger unused_mut. This is a simple way to satisfy the warning gate, rather than a claim that an empty macro must always have its own arm.

Check expansion behavior

Check empty forms, trailing commas, repeat counts, guards and or-patterns. For reusable macros, also check expressions with side effects to make sure counting or matching does not evaluate them twice. Lint behavior varies by lint and expansion context; macros are not generally exempt from warnings. The completed exercise must compile, produce the expected results and pass Clippy with warnings treated as errors.