How do I work with Bitflags for Bitwise Flag Operations in Rust?

Walkthrough

Bitflags provides a macro for defining bitwise flag types. It generates types that can be combined with |, &, ^ operators and provide methods for checking, setting, and clearing flags.

Key concepts:

  • Flags — Individual bit values
  • BitFlags — Container for combined flags
  • Operations| (union), & (intersection), ^ (xor), ! (complement)
  • Methodscontains, insert, remove, toggle

When to use Bitflags:

  • Permission systems (read, write, execute)
  • Configuration options
  • State machine flags
  • Feature toggles
  • Protocol flags

When NOT to use Bitflags:

  • Complex state (use enums or structs)
  • More than 64 flags (use HashSet or BitSet)
  • When readability is more important than efficiency

Code Examples

Basic Bitflags Definition

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Permissions: u32 {
        const READ = 0b0001;
        const WRITE = 0b0010;
        const EXECUTE = 0b0100;
    }
}
 
fn main() {
    let perms = Permissions::READ | Permissions::WRITE;
    
    println!("{:?}", perms);  // READ | WRITE
    println!("Value: {}", perms.bits());  // 3
}

Checking Flags

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug)]
    struct Flags: u8 {
        const A = 0b0000_0001;
        const B = 0b0000_0010;
        const C = 0b0000_0100;
    }
}
 
fn main() {
    let flags = Flags::A | Flags::C;
    
    // Check if specific flag is set
    println!("Has A: {}", flags.contains(Flags::A));  // true
    println!("Has B: {}", flags.contains(Flags::B));  // false
    println!("Has C: {}", flags.contains(Flags::C));  // true
    
    // Check if all specified flags are set
    println!("Has A and C: {}", flags.contains(Flags::A | Flags::C));  // true
    println!("Has A and B: {}", flags.contains(Flags::A | Flags::B));  // false
    
    // Check if any flag is set
    println!("Has any: {}", !flags.is_empty());
}

Modifying Flags

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy)]
    struct Config: u16 {
        const DEBUG = 1 << 0;
        const VERBOSE = 1 << 1;
        const LOG_FILE = 1 << 2;
        const LOG_STDERR = 1 << 3;
    }
}
 
fn main() {
    let mut config = Config::DEBUG;
    
    // Insert flags
    config.insert(Config::VERBOSE);
    println!("After insert: {:?}", config);
    
    // Remove flags
    config.remove(Config::DEBUG);
    println!("After remove: {:?}", config);
    
    // Toggle flags
    config.toggle(Config::LOG_FILE);
    println!("After toggle: {:?}", config);
    
    // Set to exact value
    config.set(Config::VERBOSE, false);
    println!("After set false: {:?}", config);
}

Bitwise Operations

use bitflags::bitflags;
 
bitflags! {
    struct Colors: u8 {
        const RED = 1;
        const GREEN = 2;
        const BLUE = 4;
    }
}
 
fn main() {
    let red = Colors::RED;
    let green = Colors::GREEN;
    
    // Union (OR)
    let yellow = red | green;  // RED | GREEN
    println!("Yellow: {:?}", yellow);
    
    // Intersection (AND)
    let both = yellow & Colors::RED;
    println!("Intersection: {:?}", both);  // RED
    
    // Difference
    let diff = yellow & !Colors::RED;
    println!("Difference: {:?}", diff);  // GREEN
    
    // XOR
    let xor = yellow ^ Colors::RED;
    println!("XOR: {:?}", xor);  // GREEN
    
    // Complement
    let complement = !Colors::RED;
    println!("Complement: {:?}", complement);
}

File Permissions Example

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy)]
    struct FilePermission: u32 {
        const OWNER_READ = 0o400;
        const OWNER_WRITE = 0o200;
        const OWNER_EXEC = 0o100;
        const GROUP_READ = 0o040;
        const GROUP_WRITE = 0o020;
        const GROUP_EXEC = 0o010;
        const OTHER_READ = 0o004;
        const OTHER_WRITE = 0o002;
        const OTHER_EXEC = 0o001;
        
        // Combined constants
        const OWNER_ALL = Self::OWNER_READ.bits() | Self::OWNER_WRITE.bits() | Self::OWNER_EXEC.bits();
        const GROUP_ALL = Self::GROUP_READ.bits() | Self::GROUP_WRITE.bits() | Self::GROUP_EXEC.bits();
        const OTHER_ALL = Self::OTHER_READ.bits() | Self::OTHER_WRITE.bits() | Self::OTHER_EXEC.bits();
    }
}
 
fn main() {
    // rwxr-xr-x
    let mode = FilePermission::OWNER_ALL 
        | FilePermission::GROUP_READ 
        | FilePermission::GROUP_EXEC
        | FilePermission::OTHER_READ
        | FilePermission::OTHER_EXEC;
    
    println!("Mode: {:o}", mode.bits());  // 755
    println!("Owner can write: {}", mode.contains(FilePermission::OWNER_WRITE));
    println!("Group can write: {}", mode.contains(FilePermission::GROUP_WRITE));
}

With Serde Serialization

use bitflags::bitflags;
use serde::{Serialize, Deserialize};
 
bitflags! {
    #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
    struct Features: u8 {
        const FEATURE_A = 1;
        const FEATURE_B = 2;
        const FEATURE_C = 4;
    }
}
 
fn main() {
    let features = Features::FEATURE_A | Features::FEATURE_C;
    
    // Serialize to JSON
    let json = serde_json::to_string(&features).unwrap();
    println!("JSON: {}", json);
    
    // Deserialize from JSON
    let parsed: Features = serde_json::from_str(&json).unwrap();
    println!("Parsed: {:?}", parsed);
}

Parsing from String

use bitflags::bitflags;
use std::str::FromStr;
 
bitflags! {
    #[derive(Debug)]
    struct Options: u32 {
        const OPT_A = 1;
        const OPT_B = 2;
        const OPT_C = 4;
    }
}
 
fn main() {
    // Parse from hex string
    let opts = Options::from_bits(0b101).unwrap();
    println!("Parsed: {:?}", opts);  // OPT_A | OPT_C
    
    // From bits truncates unknown bits
    let truncated = Options::from_bits_truncate(0b1111);
    println!("Truncated: {:?}", truncated);  // OPT_A | OPT_B | OPT_C
}

Iterating Over Flags

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug)]
    struct Flags: u8 {
        const A = 1;
        const B = 2;
        const C = 4;
        const D = 8;
    }
}
 
fn main() {
    let flags = Flags::A | Flags::C | Flags::D;
    
    // Iterate over set flags
    for flag in flags.iter() {
        println!("Flag: {:?}", flag);
    }
    
    // Count set flags
    let count = flags.iter().count();
    println!("Count: {}", count);  // 3
}

All and Empty

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug)]
    struct Flags: u8 {
        const A = 1;
        const B = 2;
        const C = 4;
    }
}
 
fn main() {
    // All flags set
    let all = Flags::all();
    println!("All: {:?}", all);  // A | B | C
    
    // No flags set
    let empty = Flags::empty();
    println!("Empty: {:?}", empty);
    
    // Check if empty
    println!("Is empty: {}", empty.is_empty());  // true
    println!("Is empty: {}", all.is_empty());    // false
}

Checking Multiple Flags

use bitflags::bitflags;
 
bitflags! {
    struct Capabilities: u16 {
        const CAN_READ = 1;
        const CAN_WRITE = 2;
        const CAN_DELETE = 4;
        const CAN_SHARE = 8;
    }
}
 
fn main() {
    let caps = Capabilities::CAN_READ | Capabilities::CAN_WRITE | Capabilities::CAN_SHARE;
    
    // Check if all of these are set
    let required = Capabilities::CAN_READ | Capabilities::CAN_WRITE;
    if caps.contains(required) {
        println!("Has read and write");
    }
    
    // Check if any of these are set
    let any_of = Capabilities::CAN_DELETE | Capabilities::CAN_SHARE;
    if caps.intersects(any_of) {
        println!("Has delete or share");
    }
    
    // Check exact match
    if caps == required | Capabilities::CAN_SHARE {
        println!("Exact match!");
    }
}

Default Implementation

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug)]
    struct Settings: u8 {
        const ENABLED = 1;
        const VERBOSE = 2;
        const DEBUG = 4;
    }
}
 
impl Default for Settings {
    fn default() -> Self {
        Settings::ENABLED | Settings::VERBOSE
    }
}
 
fn main() {
    let settings = Settings::default();
    println!("Default: {:?}", settings);  // ENABLED | VERBOSE
}

Combining with External Flags

use bitflags::bitflags;
 
bitflags! {
    struct Status: u32 {
        // Lower 8 bits for state
        const STATE_MASK = 0xFF;
        const STATE_INIT = 0;
        const STATE_RUNNING = 1;
        const STATE_STOPPED = 2;
        
        // Upper bits for flags
        const FLAG_ERROR = 1 << 8;
        const FLAG_WARNING = 1 << 9;
        const FLAG_MAINTENANCE = 1 << 10;
    }
}
 
fn main() {
    let status = Status::STATE_RUNNING | Status::FLAG_WARNING;
    
    // Extract state
    let state = status & Status::STATE_MASK;
    println!("State: {:?}", state);
    
    // Check flags
    if status.contains(Status::FLAG_ERROR) {
        println!("Error!");
    }
    if status.contains(Status::FLAG_WARNING) {
        println!("Warning!");
    }
}

Type Safety

use bitflags::bitflags;
 
bitflags! {
    struct FlagsA: u8 {
        const X = 1;
    }
}
 
bitflags! {
    struct FlagsB: u8 {
        const Y = 1;
    }
}
 
fn main() {
    let a = FlagsA::X;
    let b = FlagsB::Y;
    
    // This would be a type error:
    // let combined = a | b;  // Error: mismatched types
    
    // Must use same type
    let combined_a = a | FlagsA::from_bits_truncate(0);  // OK
}

Conditional Operations

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy)]
    struct Mode: u8 {
        const READ = 1;
        const WRITE = 2;
        const APPEND = 4;
        const CREATE = 8;
        const TRUNCATE = 16;
    }
}
 
fn open_file(filename: &str, mode: Mode) {
    println!("Opening {} with mode: {:?}", filename, mode);
    
    if mode.contains(Mode::WRITE) && mode.contains(Mode::READ) {
        println!("Read-write mode");
    }
    
    if mode.contains(Mode::CREATE) && !mode.contains(Mode::WRITE) {
        println!("Warning: CREATE without WRITE");
    }
}
 
fn main() {
    open_file("test.txt", Mode::READ | Mode::WRITE | Mode::CREATE);
}

Database Example

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct ColumnFlags: u16 {
        const PRIMARY_KEY = 1;
        const NOT_NULL = 2;
        const UNIQUE = 4;
        const INDEXED = 8;
        const AUTO_INCREMENT = 16;
        const FOREIGN_KEY = 32;
    }
}
 
struct Column {
    name: String,
    flags: ColumnFlags,
}
 
impl Column {
    fn new(name: &str) -> Self {
        Column {
            name: name.to_string(),
            flags: ColumnFlags::empty(),
        }
    }
    
    fn primary(mut self) -> Self {
        self.flags.insert(ColumnFlags::PRIMARY_KEY | ColumnFlags::NOT_NULL);
        self
    }
    
    fn unique(mut self) -> Self {
        self.flags.insert(ColumnFlags::UNIQUE | ColumnFlags::INDEXED);
        self
    }
    
    fn nullable(mut self) -> Self {
        self.flags.remove(ColumnFlags::NOT_NULL);
        self
    }
}
 
fn main() {
    let id_column = Column::new("id").primary();
    let name_column = Column::new("email").unique();
    
    println!("ID flags: {:?}", id_column.flags);
    println!("Email flags: {:?}", name_column.flags);
}

Network Protocol Flags

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug)]
    struct TcpFlags: u8 {
        const FIN = 1;
        const SYN = 2;
        const RST = 4;
        const PSH = 8;
        const ACK = 16;
        const URG = 32;
    }
}
 
fn analyze_packet(flags: TcpFlags) {
    if flags.contains(TcpFlags::SYN | TcpFlags::ACK) {
        println!("SYN-ACK packet");
    } else if flags.contains(TcpFlags::SYN) {
        println!("SYN packet (connection start)");
    } else if flags.contains(TcpFlags::FIN) {
        println!("FIN packet (connection close)");
    } else if flags.contains(TcpFlags::RST) {
        println!("RST packet (connection reset)");
    }
}
 
fn main() {
    analyze_packet(TcpFlags::SYN);
    analyze_packet(TcpFlags::SYN | TcpFlags::ACK);
    analyze_packet(TcpFlags::FIN | TcpFlags::ACK);
}

Converting to/from Primitive

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug)]
    struct Flags: u8 {
        const A = 1;
        const B = 2;
        const C = 4;
    }
}
 
fn main() {
    // Get raw bits
    let flags = Flags::A | Flags::C;
    let bits: u8 = flags.bits();
    println!("Bits: {}", bits);  // 5
    
    // From bits (returns None for invalid)
    let from_bits = Flags::from_bits(5);
    println!("From bits: {:?}", from_bits);  // Some(A | C)
    
    // From bits (truncates invalid)
    let truncated = Flags::from_bits_truncate(255);
    println!("Truncated: {:?}", truncated);  // A | B | C
    
    // Check if bits are valid
    let is_valid = Flags::is_valid_bit_pattern(255);
    println!("Valid: {}", is_valid);  // true for flags-defined bits only
}

Summary

Bitflags Key Imports:

use bitflags::bitflags;

Defining Bitflags:

bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Flags: u8 {
        const A = 1;
        const B = 2;
        const C = 4;
    }
}

Key Methods:

Method Description
.contains(flag) Check if flag is set
.insert(flag) Set flag(s)
.remove(flag) Clear flag(s)
.toggle(flag) Flip flag(s)
.set(flag, bool) Set/clear based on bool
.intersects(flag) Check if any flag matches
.is_empty() No flags set
.all() All flags set
.iter() Iterate over set flags
.bits() Get raw value

Bitwise Operators:

Operator Name Result
| Union Flags in either
& Intersection Flags in both
^ XOR Flags in one but not both
! Complement All flags not in operand

Key Points:

  • Use bitflags for efficient flag storage
  • Each flag should be a single bit
  • Combine flags with | operator
  • Check with contains() for all or intersects() for any
  • .bits() returns raw integer
  • .from_bits() validates, .from_bits_truncate() ignores unknown
  • Can derive Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize
  • Great for permissions, options, states, protocols