How do I define bit flags in Rust?

Walkthrough

The bitflags crate provides a macro for defining bitflag types — types that represent a set of boolean flags stored as bits in an integer. This is useful for configuration options, permissions, state machines, and interfacing with C APIs or hardware. The crate generates a struct with bitwise operators and utility methods for manipulating flag sets efficiently.

Key features:

  1. bitflags! macro — defines a bitflag type
  2. Bitwise operations|, &, ^, ! for combining and manipulating flags
  3. Set operationscontains, insert, remove, toggle
  4. Conversion — to/from underlying integer types
  5. Zero-cost — compiles to efficient integer operations

Code Example

# Cargo.toml
[dependencies]
bitflags = "2"
use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    struct Flags: u32 {
        const A = 0b00000001;
        const B = 0b00000010;
        const C = 0b00000100;
        const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits();
    }
}
 
fn main() {
    let flags = Flags::A | Flags::C;
    println!("Flags: {:?}", flags);
    println!("Contains A: {}", flags.contains(Flags::A));
    println!("Contains B: {}", flags.contains(Flags::B));
}

Defining Bitflags

use bitflags::bitflags;
 
bitflags! {
    /// Permissions for file access
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    pub struct Permissions: u32 {
        const READ = 0b0001;
        const WRITE = 0b0010;
        const EXECUTE = 0b0100;
        const ALL = Self::READ.bits() | Self::WRITE.bits() | Self::EXECUTE.bits();
    }
}
 
fn main() {
    // Create empty flags
    let empty = Permissions::empty();
    println!("Empty: {:?}", empty);
    
    // Create with all flags
    let all = Permissions::all();
    println!("All: {:?}", all);
    
    // Combine flags with OR
    let read_write = Permissions::READ | Permissions::WRITE;
    println!("Read + Write: {:?}", read_write);
    
    // Use predefined combination
    println!("All (predefined): {:?}", Permissions::ALL);
}
 
// Different underlying types
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    struct U8Flags: u8 {
        const FLAG_A = 0b00000001;
        const FLAG_B = 0b00000010;
        const FLAG_C = 0b00000100;
    }
}
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    struct U64Flags: u64 {
        const BIT_0 = 1 << 0;
        const BIT_10 = 1 << 10;
        const BIT_63 = 1 << 63;
    }
}
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    struct HexFlags: u32 {
        const OPTION_1 = 0x0001;
        const OPTION_2 = 0x0002;
        const OPTION_3 = 0x0004;
        const OPTION_4 = 0x0008;
    }
}

Basic Operations

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    struct Flags: u8 {
        const A = 0b0001;
        const B = 0b0010;
        const C = 0b0100;
        const D = 0b1000;
    }
}
 
fn main() {
    let mut flags = Flags::A | Flags::B;
    println!("Initial: {:?}", flags);
    
    // Contains - check if flag is set
    println!("Contains A: {}", flags.contains(Flags::A));
    println!("Contains C: {}", flags.contains(Flags::C));
    
    // Insert - add a flag
    flags.insert(Flags::C);
    println!("After insert C: {:?}", flags);
    
    // Remove - remove a flag
    flags.remove(Flags::A);
    println!("After remove A: {:?}", flags);
    
    // Toggle - flip a flag
    flags.toggle(Flags::B);
    println!("After toggle B: {:?}", flags);
    
    // Set - replace all flags
    flags.set(Flags::D, true);
    println!("After set D true: {:?}", flags);
    
    flags.set(Flags::C, false);
    println!("After set C false: {:?}", flags);
    
    // Empty and All
    println!("Empty: {:?}", Flags::empty());
    println!("All: {:?}", Flags::all());
}

Bitwise Operators

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    struct Flags: u8 {
        const A = 0b0001;
        const B = 0b0010;
        const C = 0b0100;
    }
}
 
fn main() {
    let a = Flags::A;
    let b = Flags::B;
    let ab = Flags::A | Flags::B;
    
    // OR - combine flags
    let combined = Flags::A | Flags::B | Flags::C;
    println!("A | B | C = {:?}", combined);
    
    // AND - intersect flags
    let intersect = ab & Flags::A;
    println!("(A | B) & A = {:?}", intersect);
    
    // XOR - toggle specific flags
    let xor = ab ^ Flags::A;
    println!("(A | B) ^ A = {:?}", xor);
    
    // NOT - invert all flags (within the defined bit range)
    let not = !Flags::A;
    println!("!A = {:?}", not);
    
    // Difference - remove flags
    let diff = (Flags::A | Flags::B | Flags::C) - (Flags::B | Flags::C);
    println!("Difference: {:?}", diff);
    
    // Assignment operators
    let mut flags = Flags::A;
    flags |= Flags::B;
    println!("After |= B: {:?}", flags);
    
    flags &= Flags::A | Flags::B;
    println!("After &= (A | B): {:?}", flags);
    
    flags ^= Flags::A;
    println!("After ^= A: {:?}", flags);
}

Set Operations

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    struct Permissions: u32 {
        const READ = 0b0001;
        const WRITE = 0b0010;
        const EXECUTE = 0b0100;
        const ADMIN = 0b1000;
    }
}
 
fn main() {
    let user_perms = Permissions::READ | Permissions::WRITE;
    let admin_perms = Permissions::all();
    let exec_perms = Permissions::EXECUTE;
    
    // Contains - check if all specified flags are set
    println!("User can read: {}", user_perms.contains(Permissions::READ));
    println!("User can execute: {}", user_perms.contains(Permissions::EXECUTE));
    println!("User can read & write: {}", 
        user_perms.contains(Permissions::READ | Permissions::WRITE));
    
    // Intersects - check if any flag is shared
    println!("User and exec intersect: {}", 
        user_perms.intersects(exec_perms));
    let rw_perms = Permissions::READ | Permissions::WRITE;
    println!("User and RW intersect: {}", 
        user_perms.intersects(rw_perms));
    
    // Union - combine all flags
    let combined = user_perms.union(exec_perms);
    println!("Union: {:?}", combined);
    
    // Intersection - shared flags only
    let shared = user_perms.intersection(rw_perms);
    println!("Intersection: {:?}", shared);
    
    // Difference - flags in self but not other
    let diff = admin_perms.difference(user_perms);
    println!("Admin - User: {:?}", diff);
    
    // Symmetric difference - flags in either but not both
    let sym_diff = user_perms.symmetric_difference(exec_perms);
    println!("Sym diff: {:?}", sym_diff);
    
    // Complement - all flags not set
    let complement = user_perms.complement();
    println!("Complement of user: {:?}", complement);
}

Checking Multiple Flags

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    struct Status: u8 {
        const RUNNING = 0b0001;
        const PAUSED = 0b0010;
        const ERROR = 0b0100;
        const COMPLETE = 0b1000;
    }
}
 
fn main() {
    let status = Status::RUNNING | Status::ERROR;
    
    // Check single flag
    if status.contains(Status::RUNNING) {
        println!("Process is running");
    }
    
    // Check multiple flags (all must be present)
    if status.contains(Status::RUNNING | Status::ERROR) {
        println!("Process is running with error");
    }
    
    // Check if any flag is set
    if status.intersects(Status::PAUSED | Status::ERROR) {
        println!("Process has issue (paused or error)");
    }
    
    // Check if exactly these flags
    if status == Status::RUNNING | Status::ERROR {
        println!("Exact match");
    }
    
    // Check if empty
    if status.is_empty() {
        println!("No flags set");
    } else {
        println!("Some flags set");
    }
    
    // Check if all flags are set
    if status.is_all() {
        println!("All flags set");
    } else {
        println!("Not all flags set");
    }
    
    // Complex conditions
    let can_continue = !status.contains(Status::ERROR) 
        && !status.contains(Status::PAUSED);
    println!("Can continue: {}", can_continue);
}
 
// Helper functions
fn check_status(status: Status) {
    println!("Status: {:?}", status);
    
    // Check combinations
    let active = status.contains(Status::RUNNING) 
        && !status.contains(Status::PAUSED);
    let failed = status.contains(Status::ERROR);
    let done = status.contains(Status::COMPLETE);
    
    match (active, failed, done) {
        (true, false, false) => println!("  -> Running normally"),
        (true, true, false) => println!("  -> Running with errors!"),
        (false, false, true) => println!("  -> Completed successfully"),
        (false, true, true) => println!("  -> Completed with errors"),
        _ => println!("  -> Unknown state"),
    }
}

Conversion to/from Integers

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    struct Flags: u8 {
        const A = 0b0001;
        const B = 0b0010;
        const C = 0b0100;
    }
}
 
fn main() {
    let flags = Flags::A | Flags::C;
    
    // Get bits value
    let bits: u8 = flags.bits();
    println!("Bits: {:08b}", bits);
    
    // Create from bits (unsafe - may contain undefined bits)
    let from_bits = Flags::from_bits(bits);
    println!("From bits: {:?}", from_bits);
    
    // Create from bits (truncate undefined bits)
    let truncated = Flags::from_bits_truncate(0b1111);
    println!("Truncated: {:?}", truncated);
    
    // Create from bits (retain undefined bits)
    let unchecked = Flags::from_bits_unchecked(0b1111);
    println!("Unchecked: {:?}", unchecked);
    
    // Create from bits (return None for undefined bits)
    let strict = Flags::from_bits(0b1111);
    println!("Strict: {:?}", strict);  // None (0b1000 is undefined)
    
    // Check if bits are valid
    println!("Is valid 0b0111: {}", Flags::is_valid(0b0111));
    println!("Is valid 0b1111: {}", Flags::is_valid(0b1111));
    
    // All bits
    println!("All bits: {:08b}", Flags::all().bits());
}
 
// Working with external values
fn from_external(value: u8) -> Option<Flags> {
    Flags::from_bits(value)
}
 
fn to_external(flags: Flags) -> u8 {
    flags.bits()
}
 
// Converting from integer
fn conversion_example() {
    // From known value
    let value: u8 = 0b0101;
    let flags = Flags::from_bits_truncate(value);
    println!("Flags from {}: {:?}", value, flags);
    
    // Round-trip
    let original = Flags::A | Flags::C;
    let bits = original.bits();
    let restored = Flags::from_bits(bits).unwrap();
    assert_eq!(original, restored);
}

Serde Integration

# Cargo.toml
[dependencies]
bitflags = { version = "2", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
    struct Permissions: u32 {
        const READ = 0b0001;
        const WRITE = 0b0010;
        const EXECUTE = 0b0100;
    }
}
 
fn main() {
    let perms = Permissions::READ | Permissions::WRITE;
    
    // Serialize to JSON
    let json = serde_json::to_string(&perms).unwrap();
    println!("JSON: {}", json);
    
    // Deserialize from JSON
    let restored: Permissions = serde_json::from_str(&json).unwrap();
    println!("Restored: {:?}", restored);
    
    assert_eq!(perms, restored);
}
 
// With struct
#[derive(Debug, Serialize, Deserialize)]
struct FileEntry {
    name: String,
    permissions: Permissions,
}
 
fn struct_example() {
    let entry = FileEntry {
        name: "document.txt".to_string(),
        permissions: Permissions::READ | Permissions::WRITE,
    };
    
    let json = serde_json::to_string_pretty(&entry).unwrap();
    println!("{}", json);
    
    let parsed: FileEntry = serde_json::from_str(&json).unwrap();
    println!("Parsed: {:?}", parsed);
}

Real-World Example: File Permissions

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    pub struct FileMode: u32 {
        // User permissions
        const USER_READ = 0o400;
        const USER_WRITE = 0o200;
        const USER_EXEC = 0o100;
        const USER_ALL = Self::USER_READ.bits() | Self::USER_WRITE.bits() | Self::USER_EXEC.bits();
        
        // Group permissions
        const GROUP_READ = 0o040;
        const GROUP_WRITE = 0o020;
        const GROUP_EXEC = 0o010;
        const GROUP_ALL = Self::GROUP_READ.bits() | Self::GROUP_WRITE.bits() | Self::GROUP_EXEC.bits();
        
        // Other permissions
        const OTHER_READ = 0o004;
        const OTHER_WRITE = 0o002;
        const OTHER_EXEC = 0o001;
        const OTHER_ALL = Self::OTHER_READ.bits() | Self::OTHER_WRITE.bits() | Self::OTHER_EXEC.bits();
        
        // Common combinations
        const ALL_READ = Self::USER_READ.bits() | Self::GROUP_READ.bits() | Self::OTHER_READ.bits();
        const ALL_EXEC = Self::USER_EXEC.bits() | Self::GROUP_EXEC.bits() | Self::OTHER_EXEC.bits();
    }
}
 
fn main() {
    // Standard file: rw-r--r--
    let file = FileMode::USER_READ | FileMode::USER_WRITE 
        | FileMode::GROUP_READ | FileMode::OTHER_READ;
    println!("File mode: {:o}", file.bits());
    
    // Executable: rwxr-xr-x
    let exec = FileMode::USER_ALL | FileMode::GROUP_READ | FileMode::GROUP_EXEC 
        | FileMode::OTHER_READ | FileMode::OTHER_EXEC;
    println!("Exec mode: {:o}", exec.bits());
    
    // Check permissions
    println!("User can read: {}", file.contains(FileMode::USER_READ));
    println!("User can write: {}", file.contains(FileMode::USER_WRITE));
    println!("User can execute: {}", file.contains(FileMode::USER_EXEC));
    
    // Check if anyone can read
    println!("Anyone can read: {}", file.contains(FileMode::ALL_READ));
    
    // Make executable for user
    let mut script = file;
    script.insert(FileMode::USER_EXEC);
    println!("Script mode: {:o}", script.bits());
}

Real-World Example: Window Styles

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    pub struct WindowStyle: u32 {
        const VISIBLE = 0x0001;
        const BORDER = 0x0002;
        const TITLEBAR = 0x0004;
        const RESIZABLE = 0x0008;
        const MINIMIZABLE = 0x0010;
        const MAXIMIZABLE = 0x0020;
        const CLOSABLE = 0x0040;
        const MODAL = 0x0080;
        
        // Common presets
        const STANDARD = Self::VISIBLE.bits() 
            | Self::BORDER.bits() 
            | Self::TITLEBAR.bits()
            | Self::RESIZABLE.bits()
            | Self::MINIMIZABLE.bits()
            | Self::MAXIMIZABLE.bits()
            | Self::CLOSABLE.bits();
        
        const DIALOG = Self::VISIBLE.bits() 
            | Self::BORDER.bits() 
            | Self::TITLEBAR.bits()
            | Self::CLOSABLE.bits()
            | Self::MODAL.bits();
    }
}
 
struct Window {
    title: String,
    style: WindowStyle,
}
 
impl Window {
    fn new(title: &str, style: WindowStyle) -> Self {
        Self {
            title: title.to_string(),
            style,
        }
    }
    
    fn is_visible(&self) -> bool {
        self.style.contains(WindowStyle::VISIBLE)
    }
    
    fn is_resizable(&self) -> bool {
        self.style.contains(WindowStyle::RESIZABLE)
    }
    
    fn is_modal(&self) -> bool {
        self.style.contains(WindowStyle::MODAL)
    }
    
    fn add_style(&mut self, style: WindowStyle) {
        self.style.insert(style);
    }
    
    fn remove_style(&mut self, style: WindowStyle) {
        self.style.remove(style);
    }
}
 
fn main() {
    // Standard window
    let window = Window::new("Main Window", WindowStyle::STANDARD);
    println!("Window visible: {}", window.is_visible());
    println!("Window resizable: {}", window.is_resizable());
    println!("Window modal: {}", window.is_modal());
    
    // Dialog
    let dialog = Window::new("Settings", WindowStyle::DIALOG);
    println!("\nDialog modal: {}", dialog.is_modal());
    println!("Dialog resizable: {}", dialog.is_resizable());
    
    // Custom window
    let mut custom = Window::new("Custom", WindowStyle::VISIBLE | WindowStyle::BORDER);
    println!("\nCustom resizable: {}", custom.is_resizable());
    custom.add_style(WindowStyle::RESIZABLE);
    println!("After adding: {}", custom.is_resizable());
}

Real-World Example: Configuration Options

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    pub struct ConfigFlags: u32 {
        const DEBUG = 1 << 0;
        const VERBOSE = 1 << 1;
        const LOG_FILE = 1 << 2;
        const LOG_CONSOLE = 1 << 3;
        const COLOR_OUTPUT = 1 << 4;
        const TIMESTAMP = 1 << 5;
        const THREAD_SAFE = 1 << 6;
        const AUTO_SAVE = 1 << 7;
        
        // Common presets
        const DEBUG_MODE = Self::DEBUG.bits() 
            | Self::VERBOSE.bits() 
            | Self::LOG_CONSOLE.bits()
            | Self::COLOR_OUTPUT.bits();
        
        const PRODUCTION = Self::LOG_FILE.bits() 
            | Self::THREAD_SAFE.bits() 
            | Self::AUTO_SAVE.bits();
    }
}
 
struct Logger {
    flags: ConfigFlags,
}
 
impl Logger {
    fn new(flags: ConfigFlags) -> Self {
        Self { flags }
    }
    
    fn is_debug(&self) -> bool {
        self.flags.contains(ConfigFlags::DEBUG)
    }
    
    fn is_verbose(&self) -> bool {
        self.flags.contains(ConfigFlags::VERBOSE)
    }
    
    fn should_log_to_file(&self) -> bool {
        self.flags.contains(ConfigFlags::LOG_FILE)
    }
    
    fn should_log_to_console(&self) -> bool {
        self.flags.contains(ConfigFlags::LOG_CONSOLE)
    }
    
    fn log(&self, message: &str) {
        let timestamp = if self.flags.contains(ConfigFlags::TIMESTAMP) {
            format!("[{}] ", chrono::Local::now().format("%H:%M:%S"))
        } else {
            String::new()
        };
        
        let prefix = if self.is_debug() { "[DEBUG] " } else { "" };
        
        if self.should_log_to_console() {
            println!("{}{}{}", timestamp, prefix, message);
        }
    }
}
 
fn main() {
    // Debug logger
    let debug_logger = Logger::new(ConfigFlags::DEBUG_MODE);
    println!("Debug mode: {}", debug_logger.is_debug());
    println!("Verbose: {}", debug_logger.is_verbose());
    
    // Production logger
    let prod_logger = Logger::new(ConfigFlags::PRODUCTION);
    println!("\nProduction:");
    println!("Log to file: {}", prod_logger.should_log_to_file());
    println!("Log to console: {}", prod_logger.should_log_to_console());
    
    // Custom configuration
    let mut custom_flags = ConfigFlags::LOG_CONSOLE | ConfigFlags::COLOR_OUTPUT;
    custom_flags.insert(ConfigFlags::TIMESTAMP);
    let custom_logger = Logger::new(custom_flags);
    
    println!("\nCustom logger:");
    custom_logger.log("Hello, world!");
}

Real-World Example: Protocol Flags

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    pub struct TcpFlags: u8 {
        const FIN = 0x01;
        const SYN = 0x02;
        const RST = 0x04;
        const PSH = 0x08;
        const ACK = 0x10;
        const URG = 0x20;
        const ECE = 0x40;
        const CWR = 0x80;
    }
}
 
struct TcpHeader {
    flags: TcpFlags,
    seq_num: u32,
    ack_num: u32,
}
 
impl TcpHeader {
    fn new(flags: TcpFlags) -> Self {
        Self {
            flags,
            seq_num: 0,
            ack_num: 0,
        }
    }
    
    fn is_syn(&self) -> bool {
        self.flags.contains(TcpFlags::SYN)
    }
    
    fn is_ack(&self) -> bool {
        self.flags.contains(TcpFlags::ACK)
    }
    
    fn is_syn_ack(&self) -> bool {
        self.flags.contains(TcpFlags::SYN | TcpFlags::ACK)
    }
    
    fn is_fin(&self) -> bool {
        self.flags.contains(TcpFlags::FIN)
    }
    
    fn is_rst(&self) -> bool {
        self.flags.contains(TcpFlags::RST)
    }
    
    fn flags_value(&self) -> u8 {
        self.flags.bits()
    }
}
 
fn main() {
    // SYN packet (connection initiation)
    let syn = TcpHeader::new(TcpFlags::SYN);
    println!("SYN packet: flags = {:08b}", syn.flags_value());
    
    // SYN-ACK packet (connection response)
    let syn_ack = TcpHeader::new(TcpFlags::SYN | TcpFlags::ACK);
    println!("SYN-ACK packet: flags = {:08b}", syn_ack.flags_value());
    println!("Is SYN-ACK: {}", syn_ack.is_syn_ack());
    
    // FIN packet (connection close)
    let fin = TcpHeader::new(TcpFlags::FIN | TcpFlags::ACK);
    println!("FIN-ACK packet: flags = {:08b}", fin.flags_value());
    
    // RST packet (connection reset)
    let rst = TcpHeader::new(TcpFlags::RST);
    println!("RST packet: flags = {:08b}", rst.flags_value());
    
    // Data packet with PSH-ACK
    let data = TcpHeader::new(TcpFlags::PSH | TcpFlags::ACK);
    println!("Data packet: flags = {:08b}", data.flags_value());
}

Iterating Over Flags

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    struct Flags: u8 {
        const A = 0b0001;
        const B = 0b0010;
        const C = 0b0100;
        const D = 0b1000;
    }
}
 
fn main() {
    let flags = Flags::A | Flags::C | Flags::D;
    
    // Iterate over all defined flags
    println!("All defined flags:");
    for flag in Flags::all().iter() {
        println!("  {:?} = {:04b}", flag, flag.bits());
    }
    
    // Iterate over set flags
    println!("\nSet flags in {:?}:", flags);
    for flag in flags.iter() {
        println!("  {:?}", flag);
    }
    
    // Count set flags
    let count = flags.iter().count();
    println!("\nNumber of set flags: {}", count);
    
    // Collect into vector
    let set_flags: Vec<Flags> = flags.iter().collect();
    println!("Set flags: {:?}", set_flags);
    
    // Check each flag
    println!("\nChecking each flag:");
    for flag in Flags::all().iter() {
        if flags.contains(flag) {
            println!("  {:?} is set", flag);
        } else {
            println!("  {:?} is NOT set", flag);
        }
    }
}
 
// Manual iteration
fn manual_iterate() {
    let flags = Flags::A | Flags::B;
    
    // Check each known flag
    let all_flags = [Flags::A, Flags::B, Flags::C, Flags::D];
    
    println!("Manual iteration:");
    for flag in all_flags {
        if flags.contains(flag) {
            println!("  {:?} is set", flag);
        }
    }
}

Formatting and Display

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    struct Permissions: u8 {
        const READ = 0b0001;
        const WRITE = 0b0010;
        const EXECUTE = 0b0100;
    }
}
 
fn main() {
    let perms = Permissions::READ | Permissions::WRITE;
    
    // Debug format
    println!("Debug: {:?}", perms);
    
    // Binary format
    println!("Binary: {:08b}", perms.bits());
    
    // Octal format
    println!("Octal: {:03o}", perms.bits());
    
    // Hex format
    println!("Hex: {:02x}", perms.bits());
    
    // Custom display
    display_permissions(perms);
}
 
fn display_permissions(perms: Permissions) {
    let mut parts = Vec::new();
    
    if perms.contains(Permissions::READ) {
        parts.push("READ");
    }
    if perms.contains(Permissions::WRITE) {
        parts.push("WRITE");
    }
    if perms.contains(Permissions::EXECUTE) {
        parts.push("EXECUTE");
    }
    
    if parts.is_empty() {
        println!("Permissions: none");
    } else {
        println!("Permissions: {}", parts.join(" | "));
    }
}
 
// Unix-style permission string
fn unix_permissions(perms: Permissions) -> String {
    let r = if perms.contains(Permissions::READ) { 'r' } else { '-' };
    let w = if perms.contains(Permissions::WRITE) { 'w' } else { '-' };
    let x = if perms.contains(Permissions::EXECUTE) { 'x' } else { '-' };
    format!("{}{}{}", r, w, x)
}

Combining with Enums

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    struct Modifiers: u8 {
        const SHIFT = 0b0001;
        const CTRL = 0b0010;
        const ALT = 0b0100;
        const META = 0b1000;
    }
}
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Key {
    Char(char),
    Enter,
    Escape,
    Tab,
    Backspace,
    Delete,
    ArrowUp,
    ArrowDown,
    ArrowLeft,
    ArrowRight,
}
 
#[derive(Debug, Clone, Copy)]
struct KeyEvent {
    key: Key,
    modifiers: Modifiers,
}
 
impl KeyEvent {
    fn new(key: Key) -> Self {
        Self {
            key,
            modifiers: Modifiers::empty(),
        }
    }
    
    fn with_modifiers(key: Key, modifiers: Modifiers) -> Self {
        Self { key, modifiers }
    }
    
    fn has_shift(&self) -> bool {
        self.modifiers.contains(Modifiers::SHIFT)
    }
    
    fn has_ctrl(&self) -> bool {
        self.modifiers.contains(Modifiers::CTRL)
    }
    
    fn has_alt(&self) -> bool {
        self.modifiers.contains(Modifiers::ALT)
    }
}
 
fn main() {
    // Simple key
    let key_a = KeyEvent::new(Key::Char('a'));
    println!("Key: {:?}, modifiers: {:?}", key_a.key, key_a.modifiers);
    
    // Ctrl+C
    let ctrl_c = KeyEvent::with_modifiers(Key::Char('c'), Modifiers::CTRL);
    println!("Ctrl+C: has_ctrl={}", ctrl_c.has_ctrl());
    
    // Ctrl+Shift+S
    let ctrl_shift_s = KeyEvent::with_modifiers(
        Key::Char('s'),
        Modifiers::CTRL | Modifiers::SHIFT,
    );
    println!("Ctrl+Shift+S: {:?}", ctrl_shift_s.modifiers);
    
    // Alt+ArrowUp
    let alt_up = KeyEvent::with_modifiers(Key::ArrowUp, Modifiers::ALT);
    println!("Alt+Up: has_alt={}", alt_up.has_alt());
}

Summary

  • Use bitflags! macro to define bit flag types
  • Combine flags with | operator: Flags::A | Flags::B
  • Check flags with contains(): flags.contains(Flags::A)
  • Modify flags: insert(), remove(), toggle(), set()
  • Use empty() for no flags, all() for all flags
  • Access underlying bits with .bits()
  • Convert from bits: from_bits(), from_bits_truncate(), from_bits_unchecked()
  • Iterate over set flags: flags.iter()
  • Enable serde feature for serialization support
  • Define common combinations as additional constants
  • Use #[derive(...)] inside the macro for traits like Debug, Clone, Hash
  • Bitflags are zero-cost abstractions over integer types
  • Common use cases: permissions, options, states, protocol flags