3 releases
0.0.2 | Feb 22, 2021 |
---|---|
0.0.1 | Feb 21, 2021 |
0.0.0 | Feb 6, 2021 |
#926 in Math
409,939 downloads per month
Used in 2 crates
(via stateright)
19KB
256 lines
This crate is similar to either
but
supports an unbounded number of variants. See the API
docs for examples and more details.
Contribution
Contributions are welcome! Please fork the library, push changes to your fork, and send a pull request. All contributions are shared under an MIT license unless explicitly stated otherwise in the pull request.
License
Choice is copyright 2021 Jonathan Nadal. It is made available under the MIT License.
lib.rs
:
Rust has a built in tuple (A, B, C, ...)
to represent a "product" of types. The language lacks a generic syntax for the converse:
a choice among multiple types, also known as a sum type (or "coproduct") A + B + C + ...
.
This library provides a pattern and macro to bridge this gap.
Example
// We can instantiate a "heterogenous" `Vec` without a custom `enum`.
use choice::choice;
struct A;
struct B;
struct C;
let choices: Vec<choice![A, B, C]> = vec![
choice!(0 <- A),
choice!(1 <- B),
choice!(2 <- C),
];
// Furthermore, by implementing a trait for two `Choice` forms...
use choice::{Choice, Never};
trait T {}
impl<T1: T> T for Choice<T1, Never> {}
impl<T1: T, T2: T> T for Choice<T1, T2> {}
// ... then for types that implement the trait, any `Choice` between those types also
// implements the trait.
impl T for A {}
impl T for B {}
impl T for C {}
fn f(t: impl T) {}
for x in choices {
f(x); // accepts values of type `choice![A, B, C]`
}
Composition Pattern
The underlying pattern may be a bit counterintuitive the first time you see it. The first step
is to use Choice::new
to build a base variant on top of Never
:
use choice::{Choice, Never};
let no_real_choice: Choice<u64, Never> = Choice::new(42);
The Never
type is uninhabitable and only serves to seed the pattern, so effectively we have a
"choice" between N=1 types in the example above because an instance of the type can only hold a
u64
. Calling Choice::or
extends a type to offer one more choice, inductively enabling a
choice between N+1 types.
let two_types_choice1: Choice<&'static str, Choice<u64, Never>> =
Choice::new(42).or();
You can build an instance of the same Choice
type that holds the other inner type by simply
calling Choice::new
:
let two_types_choice2: Choice<&'static str, Choice<u64, Never>> =
Choice::new("Forty two");
The above two examples share a type, so you can embed them in a collection:
let u64_or_string_vec: Vec<Choice<&'static str, Choice<u64, Never>>> = vec![
Choice::new(42).or(),
Choice::new("Forty two"),
Choice::new(24).or(),
Choice::new("Twenty four"),
];
This pattern composes to allow additional choices:
let many: Vec<Choice<&'static str, Choice<i8, Choice<char, Never>>>> = vec![
Choice::new("-INFINITY"),
Choice::new(-1 ).or(),
Choice::new('0' ).or().or(),
Choice::new(1 ).or(),
Choice::new("INFINITY" ),
];
Trait Composition
Custom enum
s serve a similar role but generally lack support for the kind of composition that
Choice
provides. For example, if types A
and B
implement trait T
, a custom enum AOrB
could also implement that trait. Unfortunately any differing choice between types would need to
reimplement this trait, e.g. necessitating a type AOrCOrD
for another scenario that needs to
choose between types A
, C
, and D
.
By implementing trait T
for Choice<A: T, Never>
and Choice<A: T, B: T>
, the trait is also
implemented for any combination of choices. See the Example section above or
alternatively
stateright::actor::Actor
for a real-world example from another library.
Macro
The choice!
macro provides syntactic sugar for a type or value of the above pattern, which
is particularly useful when there are many choices:
let x1: choice![u64, &'static str, char, String, i8] =
choice!(2 <- 'x');
let x2: Choice<u64, Choice<&'static str, Choice<char, Choice<String, Choice<i8, Never>>>>> =
Choice::new('x').or().or();
assert_eq!(x1, x2);
That macro also provides syntactic sugar for pattern matching on a Choice
. Rust is unable to
determine that the base case Never
in uninhabited, so there is also a form to appease the
exhaustiveness checker.
let c: choice![u8, char] = choice!(1 <- '?');
match c {
choice!(0 -> v) => {
panic!("Unexpected match: {}", v);
}
choice!(1 -> v) => {
assert_eq!(v, '?');
}
choice!(2 -> !) => {
unreachable!();
}
}
Features
Enable the serde
feature for serialization/deserialization.
Dependencies
~165KB