#boolean #combinator #bool #functional #adapter

no-std bool_ext

A crate which defines and implements a complete set of Option/Result-style Boolean functional combinators on Rust’s bool primitive type

12 releases (6 breaking)

0.7.0 Jul 29, 2024
0.6.2 Mar 20, 2024
0.5.3 Nov 26, 2022
0.5.1 Mar 7, 2021
0.4.2 Jul 21, 2020

#215 in Rust patterns

Download history 80/week @ 2024-07-19 234/week @ 2024-07-26 311/week @ 2024-08-02 156/week @ 2024-08-09 93/week @ 2024-08-16 194/week @ 2024-08-23 195/week @ 2024-08-30 202/week @ 2024-09-06 300/week @ 2024-09-13 257/week @ 2024-09-20 366/week @ 2024-09-27 82/week @ 2024-10-04 126/week @ 2024-10-11 197/week @ 2024-10-18 195/week @ 2024-10-25 255/week @ 2024-11-01

781 downloads per month
Used in 4 crates (3 directly)

MIT/Apache

36KB
260 lines

bool_ext

A crate which defines and implements a complete set of Boolean functional combinators.

FAQ

Aren't there already crates like this?

Yes, there are, notably Daniel Keep's boolinator, and Rust's then() and then_some() methods.

boolinator is a great crate and serves as an inspiration for this crate, as do some other sources. However, boolinator's provided set of combinators is not as complete as one might wish for (and, relatively minor nit, nor are the combinator names as short as they could be. boolinator's interface is also stable (i.e. >v1.0.0) so this would be disruptive to address.)

In Rust's case, std is conservative by design, and will only very slowly move toward a complete set of combinators for bool.

My hope is that this crate can serve as a testing ground for both the naming and scope of boolean combinators to help inform the Rust lang team's discussion and decisions with usage data and feedback.

It's just a bool--why is the API surface so large?

Well, because bool is a very versatile data type! For example, adding to a collection only if the item in question is not already present is a common operation. Map datatypes often give you this behavior by the nature of their design. Other containers such as Vec, do not. So instead of highly stateful, imperative code:

    // ...
    let mut found = false;
    for needle in haystack {
        if needle == item {
            found = true;
            break;
        }
    }    
    
    if !found {
        haystack.push(item);
    }

or imperative/declarative hybrid code:

    // ...
    if !haystack.contains(&item) {
        haystack.push(item)
    }

bool_ext enables the following highly expressive, highly cohesive, declarative code:

    // ...
    haystack.contains(&item)
            .or_do(|| haystack.push(item));

Should I use this? / I'm not sure about method-chaining/functional combinators

You are not alone! Debuggers have not yet caught up to fluent API design techniques and debugging fluent interfaces can indeed be objectively more work. On the other hand, proponents (such as me) will tell you that by elevating one's thinking from "micromanaging the CPU" to expressing one's intent by "shaping the data", far fewer bugs will be written in the first place, and the resulting code will be both more expressive and more maintainable (once the maintaining party has sufficient experience with this style of coding).

bool_ext is implemented according to Bjarne Stroustrup's now classic definition of a zero -overhead abstraction, where 1) you only pay for what you use and 2) you couldn't implement the abstraction any better if you coded it yourself by hand.

Addressing 1), the bool_ext create is very small, takes no dependencies, and most importantly , any methods defined within the crate that you do not use are stripped out by the compiler and are not a part of your resulting binary.

Regarding 2), each method is #[inline]'d, adheres to the Single Responsibility Principle, minimizes register pressure from inlining and when fully optimized (typically in release mode) should compile down to exactly the same (or better) code that could be written by hand.

What about negating a boolean?

Up until v0.4.0, bool_ext contained a _false() variant for almost every method. Thanks to input from izik1, and a lot of consideration, I decided that readability didn't suffer when using boolean_condition().not().ok() as opposed to boolean_condition().ok_false() or boolean_condition().or_ok(). I do find (!boolean_condition()).ok() is significantly less readable (because of the required parentheses and because the order of operations no longer proceeds exclusively left-to-right), but as izik1 pointed out, std::ops::Not alleviates this. Thank you, izik1! :)

License

Licensed under either:

* MIT license (see LICENSE-MIT file)
* Apache License, Version 2.0 (see LICENSE-APACHE file)

at your option.

Contributions

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions.

No runtime deps