Crates.io | match-commutative |
lib.rs | match-commutative |
version | 0.1.0 |
source | src |
created_at | 2023-06-04 12:44:10.163667 |
updated_at | 2023-09-16 12:32:38.083173 |
description | Match on patterns commutatively, reducing the use of duplicated patterns. ↔️ |
homepage | https://gitlab.com/janriemer/match-commutative |
repository | https://gitlab.com/janriemer/match-commutative |
max_upload_size | |
id | 882155 |
size | 30,239 |
https://docs.rs/match-commutative
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:
// 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!
match-commutative
insteadWith match-commutative
this can be simplified to:
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).✨
non_commut
block for operants that are not commutativeIf 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:
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,
}
In your Cargo.toml file add the following lines under [dependencies]
:
match-commutative = "0.1.0"
This crate is implemented in 100% Safe Rust, which is ensured by using #![forbid(unsafe_code)]
.
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).