#bit-flags #flags #bitmask

byteflags

A macro to generate bitflag-like structures with u8 flag values

1 unstable release

0.1.0 Jun 23, 2024

#992 in Rust patterns

MIT license

15KB
213 lines

byteflags

Rust Latest version Documentation License

byteflags implements a bitflags-like structure using a full byte for each field instead of a bit. This allows the user to maintain the ergonomics of a bitflag structure while encoding additional information within.

Usage

Add this to your Cargo.toml:

[dependencies]
byteflags = "0.1.0"

and this to your source code:

use byteflags::*;

Example

Generate a flags structure:

use byteflags::*;

byteflags! {
    /// The structure definition contains a set of named flags
    struct MyFlags {
        A = "Flag A",
        B = "Flag B",
        C = "Flag C",
        D = "Flag D",
    }
}

fn main() {
    /// The resulting structure can be used similarly to bitflags
    /// Constant values
    assert_eq!(
        MyFlags::A,
        MyFlags { A: 1, B: 0, C: 0, D: 0 }
    );
    /// Union
    assert_eq!(
        (MyFlags::A.union(MyFlags::B)),
        MyFlags { A: 1, B: 1, C: 0, D: 0 }
    );
    /// Difference
    assert_eq!(
        (MyFlags { A: 0, B: 1, C: 1, D: 1} - MyFlags {A: 0, B: 1, C: 0, D: 1 }),
        MyFlags { A: 0, B: 0, C: 1, D: 0}
    );

    /// Because each flag is a u8, you can do some arithmetic as well, including but not limited to:
    /// Addition
    assert_eq!(
        MyFlags::A + MyFlags::A + MyFlags::B,
        MyFlags { A: 2, B: 1, C: 0, D: 0 }
    );
    /// Multiplication by u8
    assert_eq!(
        (MyFlags { A: 2, B: 1, C: 0, D: 0 } * 3),
        MyFlags { A: 6, B: 3, C: 0, D: 0 }
    );
    /// Division by u8
    assert_eq!(
        (MyFlags { A: 60, B: 15, C: 0, D: 0 } / 3),
        MyFlags { A: 20, B: 5, C: 0, D: 0 }
    );
    /// Multiplication by other byteflag struct
    assert_eq!(
        (MyFlags { A: 1, B: 2, C: 3, D: 4 } * MyFlags { A: 0, B: 1, C: 2, D: 3 }),
        MyFlags { A: 0, B: 2, C: 6, D: 12 }
    );
    /// Subtractive Assignment
    let mut flags = MyFlags { A: 2, B: 2, C: 1, D: 0, };
    flags -= MyFlags::C;
    assert_eq!(
        flags,
        MyFlags { A: 2, B: 2, C: 0, D: 0 }
    );

    /// There are a couple other bells and whistles, such as:
    /// Conversion and serialization
    let flags = MyFlags { A: 0, B: 1, C: 10, D: 100 };
    assert_eq!(flags.to_vec(), vec![0, 1, 10, 100]);
    assert_eq!(serde_json::to_string(&flags).unwrap(), "[0,1,10,100]".to_string());
    assert_eq!(flags, serde_json::from_str::<MyFlags>("[0,1,10,100]").unwrap());

    /// contains() and match
    let d = MyFlags::D;
    assert_eq!(
        match d {
            MyFlags::A => "No",
            MyFlags::B => "Nope",
            MyFlags::C => "Wrong",
            MyFlags::D => "This one!",
            _ => "",
        },
        "This one!"
    );
    let flags = MyFlags { A: 0, B: 1, C: 10, D: 100 };
    assert!(flags.contains(&MyFlags::B));
    assert!(!flags.contains(&MyFlags::A));

    /// Display with labels
    assert_eq!(
        format!("{}", MyFlags::A),
        "Flag A"
    );
    assert_eq!(
        format!("{}", MyFlags::A + MyFlags::B),
        "Flag A + Flag B"
    );

    /// Access a flag via index
    assert_eq!(
        MyFlags::ALL_CONSTS[2],
        MyFlags::C
    );

    /// Get random flag with the "rand" feature
    /// Probabilities are weighted according to the flag value
    let weights = MyFlags{ A: 255, B: 127, C: 0, D: 0};
    let random_value = weights.get_random();
    // 66% chance of being MyFlags::A, 33% chance of being MyFlags::B
}

Opinionated Design Choices

These design choices might not be immediately obvious. If you require a different behavior, please feel free to submit a pull request.

use byteflags::*;

byteflags! {
    struct MyFlags {
        A = "Flag A",
        B = "Flag B",
    }
}
fn main() {
    /// All arithmetic is saturating
    assert_eq!(
        (MyFlags{ A: 200, B: 0 } + MyFlags{ A: 200, B: 0 }),
        MyFlags{ A: u8::MAX, B: 0 }
    );
    assert_eq!(
        (MyFlags::A - (MyFlags::A + MyFlags::A)),
        MyFlags::empty()
    );
    assert_eq!(
        (MyFlags{ A: 128, B: 1 } * 128),
        MyFlags{ A: u8::MAX, B: 128 }
    );

    /// contains() only checks if a value is nonzero
    assert_eq!(
        (MyFlags::A).contains(&MyFlags{ A: 100, B: 0 }),
        true
    );

    /// Display also only checks if a value is nonzero
    assert_eq!(
        format!("{}", MyFlags{ A: 5, B: 10 }),
        "Flag A + Flag B"
    );

    /// all() gives values of 1
    assert_eq!(
        MyFlags::all(),
        MyFlags{ A: 1, B: 1 }
    );
}

Dependencies

~0.7–1.6MB
~35K SLoC