#enums #three-way #haskell #capturing #package #hold #hackage

these

A three-way enum capturing This, That, or Both. Inspired by the Haskell package https://hackage.haskell.org/package/these

5 releases (3 stable)

2.0.0 Nov 11, 2020
1.1.0 Nov 11, 2020
1.0.0 Sep 18, 2019
0.1.1 Sep 8, 2019
0.1.0 Sep 8, 2019

#1463 in Rust patterns

GPL-3.0-or-later

27KB
204 lines

These

These represents a 3-way split of data. Think of it as a Result except that we have an extra case that can contain both the result T and the error E. This can be useful for when we can still compute the final result but we have also encountered an error.

enum These<T, U> {
    This(T),
    That(U),
    Both(T, U)
}

We have three constructors This which holds a T, That which holds a U, and Both which holds both.

Here and There

If we want to talk about all Ts we use the terminology Here. So this means we either have a This or Both. Or in code:

use these::These;

fn is_here<T: Copy, U: Copy>(these: &These<T, U>) -> bool {
    these.is_this() || these.is_these()
}

If we want to talk about all Us we use the terminology There. So this means we either have a That or Both. Or in code

use these::These;

fn is_here<T: Copy, U: Copy>(these: These<T, U>) -> bool {
    these.is_that() || these.is_these()
}

Contrived Example

Let us say that we have a function that only allows numbers that are less than 10. We expose a new type LessThanTen and expect our users to use is_less_than_ten to validate i8s into this type. We can use Result and model this below:

#[derive(Debug, PartialEq)]
struct LessThanTen(i8);

#[derive(Debug, PartialEq)]
pub enum Error {
    IsGreaterThanOrEqualToTen,
}

pub fn is_less_than_ten(i: i8) -> Result<LessThanTen, Error> {
    if i < 10 {
        Ok(LessThanTen(i))
    } else {
        Err(Error::IsGreaterThanOrEqualToTen)
    }
}

assert_eq!(is_less_than_ten(8), Ok(LessThanTen(8)));
assert_eq!(is_less_than_ten(10), Err(Error::IsGreaterThanOrEqualToTen));

But after a while we realise we can start to support all numbers that are less than 20. We can do a similar approach, but we would like to be backwards compatible, and also keep track of when we encounter numbers that are greater than 10. Maybe we would like to keep statistics on these errors, or convert successful results to LessThanTen for backwards compatibility. We can use These to solve this and can modelled as below:

use these::These;

#[derive(Debug, PartialEq)]
struct LessThanTen(i8);

#[derive(Debug, PartialEq)]
struct LessThanTwenty(i8);

#[derive(Debug, PartialEq)]
pub enum Error {
    IsGreaterThanOrEqualToTen,
    IsGreaterThanOrEqualToTwenty,
}

pub fn is_less_than_ten(i: i8) -> Result<LessThanTen, Error> {
    if i < 10 {
        Ok(LessThanTen(i))
    } else {
        Err(Error::IsGreaterThanOrEqualToTen)
    }
}

pub fn is_less_than_twenty(i: i8) -> These<Error, LessThanTwenty> {
    if i < 10 {
        These::That(LessThanTwenty(i))
    } else if i < 20 {
        These::Both(Error::IsGreaterThanOrEqualToTen, LessThanTwenty(i))
    } else {
        These::This(Error::IsGreaterThanOrEqualToTwenty)
    }
}

// Convert to the backwards compatible scenario
pub fn backwards_compatible(r: These<Error, LessThanTwenty>) -> Result<LessThanTen, Error> {
    r.collapse_these(
        |e| Err(e),
        |LessThanTwenty(i)| Ok(LessThanTen(i)),
        |e, _| Err(e),
    )
}

assert_eq!(is_less_than_ten(8), Ok(LessThanTen(8)));
assert_eq!(is_less_than_ten(10), Err(Error::IsGreaterThanOrEqualToTen));
assert_eq!(is_less_than_twenty(8), These::That(LessThanTwenty(8)));
assert_eq!(is_less_than_twenty(10), These::Both(Error::IsGreaterThanOrEqualToTen, LessThanTwenty(10)));
assert_eq!(is_less_than_twenty(20), These::This(Error::IsGreaterThanOrEqualToTwenty));

assert_eq!(backwards_compatible(is_less_than_twenty(8)), Ok(LessThanTen(8)));

No runtime deps