Macro delegate_match

Source
delegate_match!() { /* proc-macro */ }
Expand description

Convenience macro for writing grouped match arms for different underlying types.

Writing repetitive match arms for enumerations (or other pattern-matching constructs) — especially when types, but not the API, differ — can quickly become boilerplate. delegate_match! lets you list several patterns up-front once and then re-uses a single body for each of them, automatically expanding into equivalent ordinary Rust code.

§Syntax outline

match <scrutinee_expr> {
    [<arm_path>::]{ <entry_pat> [: <assoc_ts>][, ...] } [<arm_pat>] [if <guard_expr>] => <body_expr>[[,] ...]
}
  • arm_path — optional path prefix (e.g. MyEnum or ::std::io)
  • entry_pat — individual entry pattern, also available as the $entry_pat placeholder.
  • assoc_tsassociated tokens, also available as the $assoc_ts placeholder.
  • arm_pat — an optional pattern appended to every entry.
  • guard_expr — an optional if guard.
  • body_expr — expression generated for each entry.

This expands to:

match <scrutinee_expr> {
    <arm_path>::<entry_pat><arm_pat> if <guard_expr> => <body_expr>
}

Two placeholders are substituted for every entry before the code is type-checked, and they may appear in the following places:

  • inside the delegate arm pattern arm_pat (if present),
  • inside the match arm guard expression guard_expr (if present),
  • inside the arm body expression body_expr.

The available placeholders are:

  • $entry_pat — the entry pattern for a generated arm.
  • $assoc_ts — the tokens following an entry, up until the next one (excluding the colon).

The macro is supposed to accept standard Rust match expression syntax, extended with the above. Any other deviation should generally be considered a bug.

§Semantics

  • For each entry in a grouped arm, the macro generates a regular match arm.
  • Everything else (outer attributes, guards, commas…) is preserved exactly as you write it.
  • The only exception to that is placeholder substitution.
  • This macro performs generation before type-checking is done, so the generated code is capable of working with different types, if constructed appropriately.
  • The order of generated arms is the order of entries in the source code.

§Examples

§Delegating to the same code for multiple enum variants

use delegate_match::delegate_match;

enum MouseEvent { Scroll(i16, i16), Position(i32, i32) }
let ev = MouseEvent::Scroll(10, 20);

delegate_match! {
    match ev {
        // This expands to two individual arms.
        MouseEvent::{ Scroll, Position }(x, y) => {
            println!("mouse event: $entry_pat → ({x}, {y})")
        }
    }
}

§Using placeholders

delegate_match! {
    match msg {
        // `$assoc_ts` and `$entry_pat` are substituted at compile time.
        Msg::{ Ping: "🏓", Log: "📝" } => {
            // Outputs "🏓 Ping" or "📝 Log" depending on the variant.
            println!("{} {}", $assoc_ts, stringify!($entry_pat))
        }
    }
}

§Adding an if guard to multiple entries

delegate_match! {
    match n {
        // This works despite `val` being of different types for each variant.
        // This is because a separate arm is generated for each entry!
        Number::{ I32, I64 }(val) if val % 2 == 0 => {
            println!("even {}", val)
        }
        // We must also account for the rest of the cases.
        Number::{ I32, I64 }(val) => {
            println!("odd {}", val)
        }
    }
}