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 enums 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 Choice among types, (2) a value inhabiting a Choice, and (3) destructuring for one of these values.

Enums

Choice

Represents a choice between two types, which you can compose to represent a choice between more types -- Choice<C, Choice<A, B>> for instance.

Never

Represents an uninhabited type. This is a placeholder until the built-in never type is stabilized.