Expand description
Defines another variety of match statement.
Rather than comparing a single value against a number of patterns, the fn_match passes each
value to a given function, and evaluates the expression based on which case causes a favorable
return.
The same way a traditional match can act like a sequence of if x == y for various y,
fn_match can act like (and expands to) a sequence of if let Some(x) = foo(bar) for various
bar.
§Examples
§Quick Start
let x = fn_match! {
with fn: foo => y;
"bar" => baz(y),
"baz" => bar(y)
};expands into
let x = if let Some(y) = foo("bar") {
Some(baz(y))
} else if let Some(y) = foo("baz") {
Some(bar(y))
} else {
None
};§Detailed Example
One use case (and the motivating one) is for when using a Regex
with mutually exclusive capture groups.
let regex = Regex::new(r#"^(?:(?P<int>\d+)|(?P<str>".+")|(?P<flt>\d+\.\d+))$"#).unwrap();This regex can capture an int literal (235), a String literal ("hello"), or an (unsigned) float
literal (87.43), and could be used to generate instances of the following enum:
enum Token {
Int(u64),
Str(String),
Float(f64)
}In standard Rust, the code to generate a Token from a given &str might look like:
let input = r#""hello world""#;
let caps = regex.captures(input).expect("failed to match");
let token = if let Some(tok) = caps.name("int").map(|s| s.as_str()) {
Token::Int(tok.parse().unwrap())
} else if let Some(tok) = caps.name("str").map(|s| s.as_str()) {
Token::Str(tok.trim_matches('\"').to_string())
} else if let Some(tok) = caps.name("flt").map(|s| s.as_str()) {
Token::Float(tok.parse().unwrap())
} else { panic!("failed to match") };
assert_eq!(token, Token::Str(String::from("hello world")));Applying fn_match, as well as an extra closure, allows repetition to be
reduced, and the code to be rewritten as follows:
let input = r#"42"#;
let caps = regex.captures(input).expect("failed to match");
let f = |g| caps.name(g).map(|s| s.as_str());
let token = fn_match! {
with fn: f => tok;
"int" => Token::Int(tok.parse().unwrap()),
"str" => Token::Str(tok.trim_matches('\"').to_string()),
"flt" => Token::Float(tok.parse().unwrap())
}.expect("failed to match");
assert_eq!(token, Token::Int(42));§fn_match Syntax
There are three main components of an fn_match block:
- A function to call
- An identifier
- A sequence of lhs-rhs pairs
with fn: f => tok;This line provides a function and an identifier. The function (placed in between the fn: and
the =>) needs to take in a single value of the type that the later lines provide, and return
an Option.
Note: the function expression is placed in each if let attempt after expansion. If the
function is a closure, it is recommended to store it in a variable before the fn_match,
to prevent multiple identical closures from being written in the final code.
The identifier (between the => and ;) is used to refer to the contained Some value, if
the function returns that. (It’s used as the binding pattern for if let Some(x)).
Following this line are a sequence of match pairs.
"int" => Token::Int(tok.parse().unwrap()),The left side of the => is the testing value, to be passed into the previously provided
function.
The right side is an expression to be evaluated/returned (wrapped in its own Some) in the
event that the function returned Some when called with the left value. The rhs can also refer
to the identifier as the relevant Some value.
The entire fn_match block expression evaluates to an Option. Either
a Some containing the rhs of the lhs that matched, or None if no lhs matched.
Macros§
- fn_
match - Match based on passing each value into the given function.