Code Monkey home page Code Monkey logo

try_match-rs's Introduction

try_match

docs.rs

Provides expression macros to match a pattern on a given expression.

Basic Usage

Macros

use try_match::{try_match, match_ok, unwrap_match};

#[derive(Copy, Clone, Debug, PartialEq)]
enum Enum<T> { Var1(T), Var2 }

use Enum::{Var1, Var2};

// `try_match!` returns `Result`: `Ok(bindings)` on success or
// `Err(input_value)` otherwise
assert_eq!(try_match!(Var1(42), Var1(x)), Ok(42));
assert_eq!(try_match!(Var1(42), Var1(x) if x < 20), Err(Var1(42)));

// `match_ok!` returns `Option`
assert_eq!(match_ok!(Var1(42), Var1(x)), Some(42));
assert_eq!(match_ok!(Var1(42), Var1(x) if x < 20), None);

// `unwrap_match!` panics on failure:
assert_eq!(unwrap_match!(Var1(42), Var1(x)), 42);
unwrap_match!(Var1(42), Var1(x) if x < 20); // will panic

Bindings

// Returns `()` (wrapped by `Ok(_)`) if there are no bound variables
assert_eq!(unwrap_match!(Var1(42), Var1(_)), ());

// ... the bound value if there is exactly one binding
assert_eq!(unwrap_match!(Var1(42), Var1(x)), 42);

// ... an anonymous struct if there are multiple bindings
let vars = unwrap_match!(Var1((12, 34)), Var1((a, b)));
assert_eq!((vars.a, vars.b), (12, 34));

// ... or a tuple if the binding names are numeric
let (a, b) = unwrap_match!(Var1((12, 34)), Var1((_0, _1)));
assert_eq!((a, b), (12, 34));

// An optional `=>` clause specifies an explicit mapping
assert_eq!(unwrap_match!(Var1(42),    Var1(x) => x + 1), 43);
assert_eq!(unwrap_match!(Var2::<u32>, Var2    => "yay"), "yay");

Partial Application

// Omit the scrutinee expression to produce a closure
let _:                  Option<i32> = match_ok!(Var1(42), Var1(x));
let _: fn(Enum<i32>) -> Option<i32> = match_ok!(        , Var1(x));

Applications

Iterator::filter_map

let array = [Var1(42), Var2, Var1(10)];
let filtered: Vec<_> = array
    .iter()
    .filter_map(match_ok!(, &Var1(_0) if _0 > 20))
    .collect();
assert_eq!(filtered, [42]);

Iterator::map + Fallible Iterator::collect

let array = [Var1(42), Var2, Var1(10)];
let filtered: Result<Vec<_>, _> = array
    .iter()
    .map(try_match!(, &Var1(_0) if _0 > 20))
    .collect();

// `Var2` is the first value that doesn't match
assert_eq!(filtered, Err(&Var2));

Extract Variants

impl<T> Enum<T> {
    fn var1(&self) -> Option<&T> {
        match_ok!(self, Var1(_0))
    }

    fn is_var2(&self) -> bool {
        matches!(self, Var2)
    }
}

let enums = [Var1(42), Var2];
assert_eq!(enums[0].var1(), Some(&42));
assert_eq!(enums[1].var1(), None);

assert!(!enums[0].is_var2());
assert!(enums[1].is_var2());

Expect Certain Variants

fn this_fn_expects_var1(foo: &Enum<[u8; 4]>) {
    let (i0, i1) = unwrap_match!(foo, &Var1([_0, _, _, _1]));

    // Alternatively, you could use let-else (stabilized in Rust 1.65.0):
    // let &Var1([i0, _, _, i1]) = foo else { panic!("{foo:?}") };

    assert_eq!((i0, i1), (42, 45));
}

this_fn_expects_var1(&Var1([42, 43, 44, 45]));

Related Works

matcher::matches! (now incorporated into the standard library as core::matches!) is similar but only returns bool indicating whether matching was successful or not.

let success1 =  matches!(Some(42), Some(_));
let success2 = match_ok!(Some(42), Some(_)).is_some();
assert_eq!(success1, success2);

bind_match::bind_match! and extract::extract! behave in the same way as match_ok! except for the lack of implicit mapping and partial application.

variant::get_variant! from the extract_variant crate offers a similar functionality to match_ok!. It supports implicit mapping but uses different rules to handle multiple bindings.

License

MIT/Apache-2.0

try_match-rs's People

Contributors

yvt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

try_match-rs's Issues

Add a macro that evaluates to `Option<_>`

This issue proposes to add a variation of try_match! that evaluates to Option instead of Result.

Prior Arts

Issues

  • What should it be called? A few candidates:
    • try_match_ok!($expr, $pat) (try_match! + Result::ok)
    • match_ok!($expr, $pat) (match + Result::ok)
    • get_match!($expr, $pat)

Examples

Iterator::filter_map

let array = [Var1(42), Var2, Var1(10)];
let filtered: Vec<_> = array
    .iter()
    .filter_map(|x| try_match_ok!(x, &Var1(_0) if _0 > 20))
    .collect();
assert_eq!(filtered, [42]);

Combined with #3:

let array = [Var1(42), Var2, Var1(10)];
let filtered: Vec<_> = array
    .iter()
    .filter_map(try_match_ok!(, &Var1(_0) if _0 > 20))
    .collect();
assert_eq!(filtered, [42]);

Extracting Variants

impl<T> Enum<T> {
    fn var1(&self) -> Option<&T> {
        try_match_ok!(self, Var1(_0))
    }

    fn is_var2(&self) -> bool {
        matches!(self, Var2)
    }
}

Broken in nightly-2020-05-30

$ cargo test
   Compiling proc-macro2 v1.0.12
   Compiling unicode-xid v0.2.0
   Compiling version_check v0.9.1
   Compiling syn v1.0.18
   Compiling proc-macro-hack v0.5.15
   Compiling proc-macro-error-attr v1.0.2
   Compiling proc-macro-error v1.0.2
   Compiling quote v1.0.4
   Compiling syn-mid v0.5.0
   Compiling try_match_inner v0.1.1 ([...])
   Compiling try_match v0.2.1 ([...])
    Finished test [unoptimized + debuginfo] target(s) in 23.23s
     Running target/debug/deps/try_match-bba94c202ad14493

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/debug/deps/test-1e9c50c009d70aa6

running 2 tests
test input_evaled_only_once ... ok
test input_evaled_only_once_implicit_map ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests try_match

running 5 tests
test src/lib.rs -  (line 58) ... ok
test src/lib.rs -  (line 65) ... ok
test src/lib.rs -  (line 48) ... FAILED
test src/lib.rs -  (line 30) ... FAILED
test src/lib.rs -  (line 8) ... ok

failures:

---- src/lib.rs -  (line 48) stdout ----
error: unexpected token
 --> src/lib.rs:52:14
  |
7 | let (a, b) = try_match!(Var1((_0, _1)) = Var1((12, 34))).unwrap();
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error

Couldn't compile the test.
---- src/lib.rs -  (line 30) stdout ----
error: unexpected token
 --> src/lib.rs:35:12
  |
8 | assert_eq!(try_match!(Var1(_) = Var1(42)), Ok(()));
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: unexpected token
  --> src/lib.rs:38:12
   |
11 | assert_eq!(try_match!(Var1(x) = Var1(42)), Ok(42));
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: unexpected token
  --> src/lib.rs:41:12
   |
14 | let vars = try_match!(Var1((a, b)) = Var1((12, 34))).unwrap();
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 3 previous errors

Couldn't compile the test.

failures:
    src/lib.rs -  (line 30)
    src/lib.rs -  (line 48)

test result: FAILED. 3 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--doc'

Partial application

A new macro that acts like a curried function (e.g., matcher!($pat)($expr) == try_match!($expr, $pat)) could be useful in some situations.

Issues

  • What should it be called? What about syntax? A few candidates:
    • matcher!($pat)
    • try_matcher!($pat)
    • try_match!(_, $pat), try_match!(, $pat), try_match!($pat)
  • Should it return Option<MatchedVars> or Result<MatchedVars, Original>?
    • Related: #8
  • Should the if guard and the success expression (=> $expr) capture outer variables by move?

Examples

Iterator::filter_map

Assumes the closure returns Option<MatchedVars>

let array = [Var1(42), Var2, Var1(10)];
let filtered: Vec<_> = array
    .iter()
//  .filter_map(|x| try_match!(x, &Var1(_0) if _0 > 20).ok())
    .filter_map(matcher!(&Var1(_0) if _0 > 20))
    .collect();
assert_eq!(filtered, [42]);

Assumes the closure returns Result<MatchedVars, Original>

let array = [Var1(42), Var2, Var1(10)];
let filtered: Vec<_> = array
    .iter()
//  .filter_map(|x| try_match!(x, &Var1(_0) if _0 > 20).ok())
    .map(matcher!(&Var1(_0) if _0 > 20))
    .filter_map(Result::ok)
    .collect();
assert_eq!(filtered, [42]);

Applicable use cases in the wild: Tamschi/quote-miner, yvt/Stella2

Iterator::map + Fallible Iterator::collect

Assumes the closure returns Result<MatchedVars, Original>

let array = [Var1(42), Var2, Var1(10)];
let filtered: Result<Vec<_>, _> = array
    .iter()
    .map(matcher!(&Var1(_0) if _0 > 20))
    .collect();
// `Var2` is the first value that doesn't match
assert_eq!(filtered, Err(&Var2));

Migrate to Rust 2021 edition

Rust 2021 redefines the pat fragment specifier to handle top-level or-patterns, allowing us to simplify the macro definition somewhat.

Rust 2021 is supported by the current MSRV (1.56).

Add a macro that panics on match failure

Prior Arts

Issues

  • What should it be called? A few candidates:
    • expect_match!($expr, $pat) (match + Result::expect)
      • The mention of expect makes it clear that it may panic
    • assert_match!($expr, $pat) (match + assert!)
      • The mention of assert makes it clear that it may panic
      • Matches regex /\bassert[_a-z0-9]*\b/, helping developers locate panic hazards
      • Conflicts with std::assert_matches! (rust-lang/rust#82775)
      • Supporting => $output_expr in std::assert_matches! was decided against (rust-lang/rust#82775 (comment))
      • Traditionally, assertion macros don't produce a value
    • unwrap!($expr, $pat) (generalized Result::unwrap)
      • The mention of unwrap makes it clear that it may panic
      • Matches regex /\bunwrap\b/, helping developers locate panic hazards
      • Pattern matching doesn't necessarily "unwrap" stuff
      • It might not be clear to a reader that the second parameter represents a pattern
    • unwrap_match!($expr, $pat) (match + generalized Result::unwrap)
      • The mention of unwrap makes it clear that it may panic
      • Matches regex /unwrap/ but not /\bunwrap/b/ (this could be used to filter out non-panicking methods like Option::unwrap_or), making it more likely than unwrap! to be overlooked
      • Overlaps with matches2::unwrap_match!

Implicit tuple mapping triggers `clippy::just_underscores_and_digits`

$ cargo clippy --test test
    Checking try_match v0.3.0 (.../try_match-rs)
warning: consider choosing a more descriptive name
  --> tests/test.rs:93:47
   |
93 |     assert_eq!(try_match!(Ok::<_, &_>(42), Ok(_0) | Err(&_0)), Ok(42));
   |                                               ^^
   |
   = note: `#[warn(clippy::just_underscores_and_digits)]` on by default
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits

warning: consider choosing a more descriptive name
  --> tests/test.rs:94:49
   |
94 |     assert_eq!(try_match!(Err::<&_, _>(42), Ok(&_0) | Err(_0)), Ok(42));
   |                                                 ^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits

warning: `try_match` (test "test") generated 2 warnings
    Finished dev [unoptimized + debuginfo] target(s) in 0.23s

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.