enum-bitset 0.1.2

Generate efficient bitsets out of your enum types
Documentation

enum-bitset

Generate efficient bitsets out of your enum types.

Crates.io Version docs.rs

The generated bitset type is much like the standard HashSet or BTreeSet types in that they contain a list of ( non-repeating) values of the base enum. But being implemented as a bitset, the memory usage is typically much lower than the standard types, and all set operations (other than iteration) are constant time.

A bitset is a data structure that basically stores a list of bits, where each bit represents the presence or absence of one of the possible values of the base enum type.

The main intended use-case of the [EnumBitset] macro is an enum that represents the possible states (in the general meaning of the word) of some entity. It is common that, in some parts of your program, you need to so specify a set of those states. For example,

  • Filters:

    • The base enum represents the possible states of an entity. You want to present a list of entities to the user, allowing them to filter by one or more of those states.
    • Event subscriptions. What event types does every listener care about?
  • Grouping / Hierarchy:

    • A large state machine is divided in phases. Every phase is a "sub-state-machine". You might have an enum representing all the states of the state machine, State; and another enum Phase representing the phases. You can apply [EnumBitset] to the State enum to automatically create a StateSet enum. Then you can store the states that belong to every phase in a HashMap<Phase, StateSet>.
    • In a graph, the set of nodes that can be reached from a given Node can be represented by a NodeSet.
  • Non-exclusive values:

    • You have an enum Mode representing possible modes of operation of some part of some entity. But those modes are not mutually exclusive. You can store the modes that are active in the ModeSet type.
    • In a permission system, permissions might be represented by a Permission enum. Then, the set of permissions that a given user has can efficiently be represented by a PermissionSet.
    • What Resources are needed for a concrete task? You guess it: a ResourceSet!

Basic usage

To use, add the enum-bitset crate as a dependency,

cargo add enum-bitset

or manually edit your Cargo.toml file

[dependencies]
enum-bitset = "0.1.0"

and add the derive [EnumBitset] macro on your enum

use enum_bitset::EnumBitset;

#[derive(EnumBitset, Copy, Clone)]
enum IpAddrKind {
    V4,
    V6,
}

let mut set = IpAddrKindSet::empty();
assert!(!set.contains(IpAddrKind::V4));
assert_eq!(set.len(), 0);

set.insert(IpAddrKind::V6);
assert!(!set.contains(IpAddrKind::V4));
assert!(set.contains(IpAddrKind::V6));

let set2 = IpAddrKind::V4 | IpAddrKind::V6;
assert!(set2.contains(IpAddrKind::V4));
assert!(set2.contains(IpAddrKind::V6));
assert!(!set2.is_empty());
assert!(set2.is_all());
assert_eq!(set2, IpAddrKindSet::all());

let set3 = set2 - IpAddrKind::V4;
assert!(set3.contains(IpAddrKind::V6));
assert!(!set3.contains(IpAddrKind::V4));
assert!(!set3.is_empty());
assert!(!set3.is_all());
assert_eq!(set3, IpAddrKind::V6.into());

You can check the generated type in the example section. For more examples, see the tests directory at GitHub.

Configuration

The generated type macro can be configured using the #[bitset] attribute on the enum. As usual, the attribute must be placed after the derive attribute and before the enum declaration.

The argument of the bitset attribute is a comma-separated list of key-value pairs. The following keys are supported:

Please, read the documentation of the crate to find the full list of supported keys ans values.

Alternatives

This crate exists because at work I had recurrent use cases where I needed to represent a set of values out of an enum. While there are high-quality crates in the ecosystem that solve similar problems, after much experimentation I found that my set of trade-offs and usability preferences were a bit different from the crates in the ecosystem.

Of course, this doesn't mean that existing crates are not good. Quite the opposite, being more mature and created by more experienced developers, they are probably much better than this crate. I am publishing this crate hoping that it might prove useful to someone else with similar preferences as me.

Here are some of the existing crates in the ecosystem.

enumset

This is an incredibly good and feature complete crate, widely used. It supports enums with more than 128 variants.

My main concern with enumset is that the set type generated is generic over the base enum. That is, you use it as EnumSet<MyEnum>, where the EnumSet<T> type lives in the enumset crate. That means that you cannot add inherent methods to the generated type. This can be worked around by using an extension trait or a new-type wrapper, but it turned out to be cumbersome for my use cases.

If that limitation is not a problem for you, then I would recommend using enumset as a very mature and high-quality crate.

Note that enumset remains truthful to what a derive macro is originally meant to do: implement a trait on the type it is applied to[^2]. In their case, they implement an EnumSetType trait, which is the requirement to use an enum as the generic parameter of their EnumSet type.

[^2]: In contrast, our crate enum-bitset sort of abuses the derive macro to create a new type. This kind of (ab)use is not uncommon in the Rust ecosystem. Well, you could say it is a "derived type"... right?

bitfield

High-quality and well-known crate. However, its approach is very different: it allows generating structs that represent bitfields, creating getters and setters to manipulate ranges of bits in the underlying value. Therefore, the generated type does not have a relation with an enum.

bitflags

Another high-quality and well-known crate. However, its philosophy is different: instead of generating a set type derived from an existing enum, it creates a structure with constants that represent a combination of flags. Its documentation explicitly states that it is not intended to be used as a bitfield.

bitvec

Yet another high-quality and well-known crate. But once again, with a very different, lower-level, approach. It contains types that work muck like std's collections of booleans, but using a one-bit-per-bool approach.