#variant #sum #coproduct #union #either

choice

Similar to either but supports an unbounded number of variants

3 releases

0.0.2 Feb 22, 2021
0.0.1 Feb 21, 2021
0.0.0 Feb 6, 2021

#924 in Math

Download history 26010/week @ 2023-12-13 14515/week @ 2023-12-20 9508/week @ 2023-12-27 25399/week @ 2024-01-03 29276/week @ 2024-01-10 35922/week @ 2024-01-17 25908/week @ 2024-01-24 26220/week @ 2024-01-31 29887/week @ 2024-02-07 27217/week @ 2024-02-14 41432/week @ 2024-02-21 39341/week @ 2024-02-28 34876/week @ 2024-03-06 31162/week @ 2024-03-13 33894/week @ 2024-03-20 29964/week @ 2024-03-27

138,155 downloads per month
Used in 2 crates (via stateright)

MIT license

19KB
256 lines

crates.io docs.rs LICENSE

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 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.

Dependencies

~180KB