match-commutative ↔️

Match on patterns commutatively, reducing the use of duplicated patterns.


Pipeline Status Crates.io version Downloads docs.rs docs

-------------- ## Documentation [https://docs.rs/match-commutative](https://docs.rs/match-commutative) ## Motivation When you need to `match` on three values that form a commutative math relation, you often need to duplicate a lot of patterns. Let's look at an example of what this might look like: ```rust // imagine that these values come from somewhere and we need to match on them let operant1 = Operant::Str(Some("42".into())); let operant2 = Operant::Num(Some(1)); let operator = Operator::Plus; match (operant1, operator, operant2) { ( Operant::Str(Some(operant_str)), Operator::Plus, Operant::Num(Some(operant_num)), ) | ( Operant::Num(Some(operant_num)), Operator::Plus, Operant::Str(Some(operant_str)), ) if operant_str.len() < 3 => { let result = operant_num + operant_str.parse::().unwrap(); println!("Result is: {}", result); } ( Operant::Str(Some(operant_str)), Operator::Mult, Operant::Num(Some(operant_num)), ) | ( Operant::Num(Some(operant_num)), Operator::Mult, Operant::Str(Some(operant_str)), ) if operant_str.len() < 3 => { let result = operant_num * operant_str.parse::().unwrap(); println!("Result is: {}", result); } (_, _, _) => { panic!("Not relevant for this example") } } // Types that we use in this example enum Operant { Str(Option), Num(Option), } enum Operator { Plus, Mult, Minus, } ``` For both `Operator::{Plus, Mult}`, we have to write _two patterns each_ that are exactly identical (and connect them with `|` (or-pattern)) and execute the same logic. The only difference in the pattern is the ordering of the `Operant`. Not nice! ### Using `match-commutative` instead With `match-commutative` this can be simplified to: ```rust use match_commutative::match_commutative; // imagine that these values come from somewhere and we need to match on them let operant1 = Operant::Str(Some("42".into())); let operant2 = Operant::Num(Some(1)); let operator = Operator::Plus; match_commutative!( operant1, operator, operant2, Operant::Str(Some(operant_str)), Operator::Plus, Operant::Num(Some(operant_num)) if operant_str.len() < 3 => { let result = operant_num + operant_str.parse::().unwrap(); println!("Result is: {}", result); }, Operant::Str(Some(operant_str)), Operator::Mult, Operant::Num(Some(operant_num)) if operant_str.len() < 3 => { let result = operant_num * operant_str.parse::().unwrap(); println!("Result is: {}", result); } non_commut { _, _, _ => { // in `non_commut` block, patterns and their execution block behave exactly like std Rust panic!("Not relevant for this example") } } ); // Types that we use in this example enum Operant { Str(Option), Num(Option), } enum Operator { Plus, Mult, Minus, } ``` Note that in the above example the values of `operant1` and `operant2` could have been swapped, __while still leading to the same program output.__ So we have successfully avoided the expression of _ordering_ in our patterns (where ordering is not needed between two `Operant`s).✨ ### Using `non_commut` block for operants that are not commutative If you need to `match` on operants that are not commutative, you can put the pattern in the optional `non_commut` block. Within `non_commut` patterns behave exactly like std Rust: ```rust use match_commutative::match_commutative; let operant1 = Operant::Str(Some("42".into())); let operant2 = Operant::Num(Some(1)); let operator = Operator::Minus; // a non-commutative operator! let result = match_commutative!( operant2, operator, operant1, Operant::Str(_), Operator::Plus, Operant::Num(_) => { // do something here todo!() } non_commut { // for minus operations, we get different results depending on the // ordering of the operants Operant::Num(Some(op_num)), Operator::Minus, Operant::Str(Some(op_str)) if op_str.len() < 3 => { op_num - op_str.parse::().unwrap() }, Operant::Str(Some(op_str)), Operator::Minus, Operant::Num(Some(op_num)) if op_str.len() < 3 => { op_str.parse::().unwrap() - op_num }, _,_,_ => { // catch all match arm todo!() } } ); assert_eq!(-41, result); // Types that we use in this example enum Operant { Str(Option), Num(Option), } enum Operator { Plus, Mult, Minus, } ``` ## Getting Started In your Cargo.toml file add the following lines under `[dependencies]`: ```toml match-commutative = "0.1.0" ``` ## Safety This crate is implemented in __100% Safe Rust__, which is ensured by using `#![forbid(unsafe_code)]`. ## MSRV The Minimum Supported Rust Version for this crate is __1.54__. An increase of MSRV will be indicated by a minor change (according to SemVer).
-------
#### License Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.