<h1 style="text-align: center;">match-commutative ↔️</h1>
<h3 style="text-align: center;">
Match on patterns commutatively, reducing the use of duplicated patterns.
</h3>
<br>
<p style="text-align: center;">
<a href="https://gitlab.com/janriemer/match-commutative/-/commits/main">
<img alt="Pipeline Status" src="https://img.shields.io/gitlab/pipeline-status/janriemer/match-commutative?branch=main&color=B5E8E0&logoColor=D9E0EE&label=pipeline&labelColor=302D41&style=for-the-badge" />
</a>
<a href="https://crates.io/crates/match-commutative">
<img src="https://img.shields.io/crates/v/match-commutative.svg?style=for-the-badge&logo=rust&color=C9CBFF&logoColor=D9E0EE&labelColor=302D41"
alt="Crates.io version" />
</a>
<a href="https://crates.io/crates/match-commutative">
<img src="https://img.shields.io/crates/d/match-commutative.svg?style=for-the-badge&color=F2CDCD&logoColor=D9E0EE&labelColor=302D41"
alt="Downloads" />
</a>
<a href="https://docs.rs/match-commutative">
<img src="https://img.shields.io/badge/docs-latest-blue.svg?style=for-the-badge&color=DDB6F2&logoColor=D9E0EE&labelColor=302D41"
alt="docs.rs docs" />
</a>
</p>
--------------
## 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::<isize>().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::<isize>().unwrap();
println!("Result is: {}", result);
}
(_, _, _) => {
panic!("Not relevant for this example")
}
}
// Types that we use in this example
enum Operant {
Str(Option<String>),
Num(Option<isize>),
}
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::<isize>().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::<isize>().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<String>),
Num(Option<isize>),
}
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::<isize>().unwrap()
},
Operant::Str(Some(op_str)),
Operator::Minus,
Operant::Num(Some(op_num)) if op_str.len() < 3 => {
op_str.parse::<isize>().unwrap() - op_num
},
_,_,_ => {
// catch all match arm
todo!()
}
}
);
assert_eq!(-41, result);
// Types that we use in this example
enum Operant {
Str(Option<String>),
Num(Option<isize>),
}
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).
<br>
-------
<br>
#### License
<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>
<br>
<sub>
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.
</sub>