1 unstable release
0.1.0 | May 8, 2023 |
---|
#203 in #group
10KB
130 lines
Groups of optional values where at least one value is present
Motivation
Sometimes applications need to ensure at least one value is present, while also allowing
multiple values of distinct types. A common pattern in this case, is to track multiple Option
values and require at least one contains a value:
/// Represent distinct optional values for a couple of types:
struct NameId {
id: Option<u64>,
name: Option<String>,
}
impl NameId {
fn ensure_value_is_present(&self) -> Result<(), &'static str> {
if self.id.is_some() || self.name.is_some() {
Ok(())
} else {
Err("either an id or a name or both must be present")
}
}
}
let good_value = NameId { id: Some(42), name: None };
assert!(good_value.ensure_value_is_present().is_ok());
let bad_value = NameId { id: None, name: None };
assert!(bad_value.ensure_value_is_present().is_err());
Overview
This crate provides types to represent this pattern:
Sometimes applications need to represent 1 or more values which may have distinct types. This
is where someval
comes in to "make illegal states unrepresentable" for this pattern.
A "someval" is a type similar to a set of Option
values except it guarantees at type-checking
time that at least one value is always present at runtime.
The "someval" types
The "someval" types in this crate follow common structure, varying in the number of generics
they support. They are all named SomeN
with the suffix N
indicating how many distinct values
are possible, e.g. [Some2], [Some3], etc… So [Some2] is generic over two types: Some2<A, B>
,
while [Some3] is generic over three: Some3<A, B, C>
, etc…
Each "someval" type is an enum. Both type parameters and enum variants use the uppercase English alphabet as placeholders, e.g.:
use someval::Some2;
type NameId = Some2<u64, String>;
let nid = NameId::A(42);
There is an enum variant for every combination of values present:
use someval::Some3;
type Triple = Some3<i64, &'static str, bool>;
let val = Triple::AC(42, false);
Constructing "somevals"
Values can be constructed with the enum variants:
use someval::Some2;
type NameId = Some2<u64, String>;
let nid1 = NameId::A(42);
let nid2 = NameId::B("Alice".to_string());
let nid3 = NameId::AB(13, "Bob".to_string());
A value with all types present can be converted from a tuple of values via From
:
let nid4 = NameId::from((13, "Bob".to_string()));
assert_eq!(nid3, nid4);
A value can by fallibly converted from a tuple of Option
values via TryFrom
:
let res1 = NameId::try_from((Some(42), None));
assert!(res1.is_ok());
let res2 = NameId::try_from((None, None));
assert!(res2.is_err());
A "someval" like [Some2] can always be converted to a tuple of Option
values:
type NameId = Some2<u64, String>;
let nid = NameId::A(42);
let (optid, optname) = nid.into();
assert_eq!(optid, Some(42));
assert_eq!(optname, None);
Most methods on a "someval" use a copy/move receiver and each provides an as_ref
method to
convert to references (similar to Option::as_ref and Result::as_ref, not to be confused
with AsRef::as_ref):
type NameId = Some2<u64, String>;
let nid = NameId::A(42);
let idref: &u64 = match nid.as_ref() {
Some2::A(x) => x,
_ => panic!(),
};
assert_eq!(*idref, 42);
Individual accessor methods give an Option
for components (similar to Result::ok and
Result::err):
type NameId = Some2<u64, String>;
let nid = NameId::A(42);
assert_eq!(nid.as_ref().a(), Some(&42));
assert_eq!(nid.as_ref().b(), None);