Skip to content

Latest commit

 

History

History
227 lines (180 loc) · 6.54 KB

README.md

File metadata and controls

227 lines (180 loc) · 6.54 KB

Build Status Rust Version 1.65+ Crate Docs License

Welcome to FlagSet!

FlagSet is a new, ergonomic approach to handling flags that combines the best of existing crates like bitflags and enumflags without their downsides.

Existing Implementations

The bitflags crate has long been part of the Rust ecosystem. Unfortunately, it doesn't feel like natural Rust. The bitflags crate uses a wierd struct format to define flags. Flags themselves are just integers constants, so there is little type-safety involved. But it doesn't have any dependencies. It also allows you to define implied flags (otherwise known as overlapping flags).

The enumflags crate tried to improve on bitflags by using enumerations to define flags. This was a big improvement to the natural feel of the code. Unfortunately, there are some design flaws. To generate the flags, procedural macros were used. This implied two separate crates plus additional dependencies. Further, enumflags specifies the size of the flags using a repr($size) attribute. Unfortunately, this attribute cannot resolve type aliases, such as c_int. This makes enumflags a poor fit for FFI, which is the most important place for a flags library. The enumflags crate also disallows overlapping flags and is not maintained.

FlagSet improves on both of these by adopting the enumflags natural feel and the bitflags mode of flag generation; as well as additional API usage niceties. FlagSet has no dependencies and is extensively documented and tested. It also tries very hard to prevent you from making mistakes by avoiding external usage of the integer types. FlagSet is also a zero-cost abstraction: all functions are inlineable and should reduce to the core integer operations. FlagSet also does not depend on stdlib, so it can be used in no_std libraries and applications.

Defining Flags

Flags are defined using the flags! macro:

use flagset::{FlagSet, flags};
use std::os::raw::c_int;

flags! {
    enum FlagsA: u8 {
        Foo,
        Bar,
        Baz,
    }

    enum FlagsB: c_int {
        Foo,
        Bar,
        Baz,
    }
}

Notice that a flag definition looks just like a regular enumeration, with the addition of the field-size type. The field-size type is required and can be either a type or a type alias. Both examples are given above.

Also note that the field-size type specifies the size of the corresponding FlagSet type, not size of the enumeration itself. To specify the size of the enumeration, use the repr($size) attribute as specified below.

Flag Values

Flags often need values assigned to them. This can be done implicitly, where the value depends on the order of the flags:

use flagset::{FlagSet, flags};

flags! {
    enum Flags: u16 {
        Foo, // Implicit Value: 0b0001
        Bar, // Implicit Value: 0b0010
        Baz, // Implicit Value: 0b0100
    }
}

Alternatively, flag values can be defined explicitly, by specifying any const expression:

use flagset::{FlagSet, flags};

flags! {
    enum Flags: u16 {
        Foo = 0x01,   // Explicit Value: 0b0001
        Bar = 2,      // Explicit Value: 0b0010
        Baz = 0b0100, // Explicit Value: 0b0100
    }
}

Flags can also overlap or "imply" other flags:

use flagset::{FlagSet, flags};

flags! {
    enum Flags: u16 {
        Foo = 0b0001,
        Bar = 0b0010,
        Baz = 0b0110, // Implies Bar
        All = (Flags::Foo | Flags::Bar | Flags::Baz).bits(),
    }
}

Specifying Attributes

Attributes can be used on the enumeration itself or any of the values:

use flagset::{FlagSet, flags};

flags! {
    #[derive(PartialOrd, Ord)]
    enum Flags: u8 {
        Foo,
        #[deprecated]
        Bar,
        Baz,
    }
}

Collections of Flags

A collection of flags is a FlagSet<T>. If you are storing the flags in memory, the raw FlagSet<T> type should be used. However, if you want to receive flags as an input to a function, you should use impl Into<FlagSet<T>>. This allows for very ergonomic APIs:

use flagset::{FlagSet, flags};

flags! {
    enum Flags: u8 {
        Foo,
        Bar,
        Baz,
    }
}

struct Container(FlagSet<Flags>);

impl Container {
    fn new(flags: impl Into<FlagSet<Flags>>) -> Container {
        Container(flags.into())
    }
}

assert_eq!(Container::new(Flags::Foo | Flags::Bar).0.bits(), 0b011);
assert_eq!(Container::new(Flags::Foo).0.bits(), 0b001);
assert_eq!(Container::new(None).0.bits(), 0b000);

Operations

Operations can be performed on a FlagSet<F> or on individual flags:

Operator Assignment Operator Meaning
| |= Union
& &= Intersection
^ ^= Toggle specified flags
- -= Difference
% %= Symmetric difference
! Toggle all flags

Optional Serde support

Serde support can be enabled with the 'serde' feature flag. You can then serialize and deserialize FlagSet<T> to and from any of the supported formats:

use flagset::{FlagSet, flags};

flags! {
    enum Flags: u8 {
        Foo,
        Bar,
    }
}

let flagset = Flags::Foo | Flags::Bar;
let json = serde_json::to_string(&flagset).unwrap();
let flagset: FlagSet<Flags> = serde_json::from_str(&json).unwrap();
assert_eq!(flagset.bits(), 0b011);

For serialization and deserialization of flags enum itself, you can use the serde_repr crate (or implement serde::ser::Serialize and serde:de::Deserialize manually), combined with the appropriate repr attribute:

use flagset::{FlagSet, flags};
use serde_repr::{Serialize_repr, Deserialize_repr};

flags! {
   #[repr(u8)]
   #[derive(Deserialize_repr, Serialize_repr)]
   enum Flags: u8 {
        Foo,
        Bar,
   }
}

let json = serde_json::to_string(&Flags::Foo).unwrap();
let flag: Flags = serde_json::from_str(&json).unwrap();
assert_eq!(flag, Flags::Foo);