Crate guard

source ·
Expand description

This crate exports a macro which implements most of RFC 1303 (a “let-else” or “guard” expression as you can find in Swift).

The syntax proposed in the RFC was if !let PAT = EXPR { BODY } or let PAT = EXPR else { BODY } (where BODY must diverge).

  1. The newer syntax, guard!(let PAT = EXPR else { BODY }). If added to the language, this would be ambiguous (consider let foo = if bar { baz() } else { quux() } – is it a let statement or a let else statement?), however in the context of this macro there is no ambiguity because only let else statements are allowed.

    A variation suggested in the RFC discussion, guard!(let PAT else { BODY } = EXPR), is also supported.

  2. The older syntax, guard!({ BODY } unless EXPR => PAT). This should be a little faster to compile than the newer syntax, which requires an initial loop through the token stream to separate the pattern, expression and body (the normal macro_rules! syntax is not quite powerful enough).

Example usage:

#[macro_use] extern crate guard;

// read configuration from a certain environment variable
// do nothing if the variable is missing
guard!(let Ok(foo) = env::var("FOO")
       else { return });

println!("FOO = {}", foo);

Unlike if let, pattern guards are allowed in PAT, and, if using the new syntax, unlike match you don’t get a “cannot bind by-move” error:

guard!(let Ok(foo) if foo.starts_with("bar") = env::var("FOO")
       else { return });

The BODY expression is enforced to be diverging at compile time (with the naive expansion to if let or match, it would be allowed to return fallback values for the bindings instead of diverging, which would violate the semantics of the guard statement, namely that code after it never executes if the pattern doesn’t match).

Limitations

  1. Expressions in the pattern are not supported. This is a limitation of the current Rust macro system – I’d like to say “parse an identifier in this position, but if that fails try parsing an expression” but this is is impossible; I can only test for specific identifiers. It’s easy to get around this restriction: use a pattern guard (as in match) instead.

  2. Empty, un-namespaced enum variants and structs cause the expansion to fail, because the macro thinks they are identifiers. It’s possible to get around this as well:

    a. For empty enum variants, include the enum name as in Enum::Empty. b. For unit-like structs, namespace it as in namespace::Empty, or use Empty {}.

  3. PAT cannot be irrefutable. This is the same behavior as if let and match, and it’s useless to write a guard with an irrefutable pattern anyway (you can just use let), so this shouldn’t be an issue. This is slightly more annoying than it could be due to limitation #1. Nonetheless, if #14252 is ever fixed, irrefutable patterns could be allowed by inserting a no-op pattern guard into the expansion.

Macros

  • Match a pattern to an expression, binding identifiers in the calling scope. Diverge if the match fails.
  • Match a pattern to an expression, binding identifiers in the calling scope. Panic if the match fails.

Enums