Crate choice[−][src]
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.
Re-exports
pub use Choice::L; |
pub use Choice::R; |
Macros
choice | Syntactic sugar for (1) a |
Enums
Choice | Represents a choice between two types, which you can compose to represent a choice between more
types -- |
Never | Represents an uninhabited type. This is a placeholder until the built-in never type is stabilized. |