23

I have an enum that looks like this:

#[repr(u8)]
pub enum PublicFlags {
    PublicFlagVersion = 0x01,
    PublicFlagReset = 0x02,
    NoncePresent = 0x04,
    IdPresent = 0x08,
    PktNumLen4 = 0x30,
    PktNumLen2 = 0x20,
    PktNumLen1 = 0x10,
    Multipath = 0x40,
}

I want to do a bitwise operation on several of the enum values. However, the Rust compiler complains:

an implementation of `std::ops::BitAnd` might be missing for `PublicFlags`.
Brett Jackson
  • 617
  • 2
  • 9
  • 22

4 Answers4

29

An enum in Rust is not intended to be used as bit flags. PublicFlags can only take the values given in the enum (and not a combination). So for instance, the following match statement is exhaustive:

let flags: PublicFlags;
...
match flags {
    PublicFlagVersion => {...}
    PublicFlagReset => {...}
    NoncePresent => {...}
    IdPresent => {...}
    PktNumLen4 => {...}
    PktNumLen2 => {...}
    PktNumLen1 => {...}
    Multipath => {...}
}

There is no way to have a PublicFlags variable with a combination of the flags.

The solution is to actually store the value as a u8, then use constants to store the value of each flag. This can be cumbersome, but thankfully the bitflags crate wraps all the boilerplate up in a macro for you. Here is an example how you would create your struct using bitflags:

bitflags::bitflags! {
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
    struct PublicFlags: u8 {
        const PUBLIC_FLAG_VERSION = 0x01;
        const PUBLIC_FLAG_RESET = 0x02;
        const NONCE_PRESENT = 0x04;
        const ID_PRESENT = 0x08;
        const PKT_NUM_LEN_4 = 0x30;
        const PKT_NUM_LEN_2 = 0x20;
        const PKT_NUM_LEN_1 = 0x10;
        const MULTIPATH = 0x40;
    }
}

fn main() {
    let flag = PublicFlags::PUBLIC_FLAG_VERSION | PublicFlags::ID_PRESENT;
    assert!((flag & PublicFlags::MULTIPATH).is_empty()); 
    assert!(flag.contains(PublicFlags::ID_PRESENT));
} 
Cole Tobin
  • 9,206
  • 15
  • 49
  • 74
Djzin
  • 1,148
  • 9
  • 11
11

This could work as an alternative answer without new dependencies.

pub mod PublicFlags {
    pub const PublicFlagVersion: u8 = 0x01;
    pub const PublicFlagReset: u8 = 0x02;
    pub const NoncePresent: u8 = 0x04;
    pub const IdPresent: u8 = 0x08;
    pub const PktNumLen4: u8 = 0x30;
    pub const PktNumLen2: u8 = 0x20;
    pub const PktNumLen1: u8 = 0x10;
    pub const Multipath: u8 = 0x40;
}

You can refer to the values just like with an enum with PublicFlags::PublicFlagVersion, or add use PublicFlags::*; if it is cleaner to reference the values without specifying the namespace.

Sasha Kondrashov
  • 4,038
  • 1
  • 18
  • 29
  • 3
    This solution has one big drawback: When using a match statement, the compiler can detect missing cases if you are using enums. This won't work with this solution. – Phidelux Oct 11 '22 at 10:59
1

I don't know if this existed back then, but a bit of as magic does the job without any bitor/bitand trait implementation (makes the code a bit longer but it is very simple)

enum BITS {
    ONE = 0b0001,
    TWO = 0b0010,
    FOUR = 0b0100,
    EIGHT = 0b1000,
}

fn main() {
    let flags: u8;

    flags = BITS::ONE as u8 | BITS::FOUR as u8 | BITS::EIGHT as u8;

    println!("{flags:b}")

    use_flags(flags);
}

fn use_flags(flags:u8){
    let flag_one = flags & BITS::ONE as u8 == 1;
    if flag_one {
        println!("flag ONE is {flag_one}")
    }
}

PS: you will get warning of this shape if you don't use all enums in the code: "warning: variant TWO is never constructed", but, again, it is just a warning that can be handled gracefully.

NOTE: when using enums for bitwise operations, item values should not overlap. In the code of the main question, at least PktNumLen1 will overlap with PublicFlagReset and IdPresent, 0x10 is 0b1010, 0x8 is 0b1000, and 0x2 is 0b0010 (2nd and 4th bits overlap)

Yılmaz Durmaz
  • 2,374
  • 12
  • 26
0

I have already an answer here on how to setup and use an enum if we want bitwise operations.

On the other hand, we can combine enum values in a vector (dynamic array) and use them as they are:

#[derive(Debug)]
enum BITS {
    ONE = 0b0001,
    TWO = 0b0010,
    FOUR = 0b0100,
    EIGHT = 0b1000,
}

fn main() {
    let flags: Vec<BITS>;

    flags = vec![BITS::ONE, BITS::FOUR, BITS::EIGHT];

    println!("{flags:?}");

    use_flags(flags);
}

fn use_flags(flags:Vec<BITS>){
    for flag in flags{
        match flag {
            BITS::ONE => println!("flag ONE is set"),
            BITS::TWO => println!("flag TWO is set"),
            BITS::FOUR => println!("flag FOUR is set"),
            BITS::EIGHT => println!("flag EIGHT is set")
        }
    }
}

the thing here is to set default values (with mut) inside our function and use the match to customize them later down the code.

it is easy to code but the downside of this approach is the memory usage for each flag inside the vector.

in the bit operation approach, bits of enum values should not overlap. the forethought process is essential in that approach. and also unpacking bit fields is not easy, yet storing them in bit fields makes it efficient for memory. so check my other answer too. (https://stackoverflow.com/a/76603396/9512475)

Yılmaz Durmaz
  • 2,374
  • 12
  • 26