What is the difference between bitflags::Flags::bits and from_bits_retain for flag value handling?

bitflags::Flags::bits and from_bits_retain handle unknown bits—bits set in a value that don't correspond to any defined flag—differently. The bits() method returns the raw underlying bits value, potentially including unknown bits depending on how the flags were constructed. The from_bits_retain() constructor creates a flags value from bits while preserving any unknown bits, allowing round-trip conversion without data loss. This contrasts with from_bits() which returns None if unknown bits are present, making from_bits_retain() essential for bitwise operations that may set bits outside the defined flags.

Basic Flags Definition

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct Permissions: u32 {
        const READ = 0b001;
        const WRITE = 0b010;
        const EXECUTE = 0b100;
    }
}
 
fn main() {
    let perms = Permissions::READ | Permissions::WRITE;
    println!("Permissions: {:?}", perms);
    println!("Bits: {:03b}", perms.bits());
}

Bitflags are defined with named constants representing individual bit positions.

The bits() Method

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct Status: u8 {
        const ACTIVE = 0b00000001;
        const PENDING = 0b00000010;
        const VERIFIED = 0b00000100;
    }
}
 
fn main() {
    let status = Status::ACTIVE | Status::PENDING;
    
    // bits() returns the raw underlying value
    let raw_bits: u8 = status.bits();
    
    println!("Status: {:?}", status);
    println!("Raw bits: {:08b} ({})", raw_bits, raw_bits);
    
    // Check individual flags
    println!("Has ACTIVE: {}", status.contains(Status::ACTIVE));
    println!("Has PENDING: {}", status.contains(Status::PENDING));
    println!("Has VERIFIED: {}", status.contains(Status::VERIFIED));
}

bits() returns the raw numeric value stored in the flags type.

From Bits Conversion

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct Color: u8 {
        const RED = 0b001;
        const GREEN = 0b010;
        const BLUE = 0b100;
    }
}
 
fn main() {
    // from_bits: returns None if unknown bits present
    let known = Color::from_bits(0b101); // RED | BLUE
    println!("Known bits: {:?}", known);
    
    let unknown = Color::from_bits(0b1001); // Has unknown bit
    println!("Unknown bits: {:?}", unknown); // None
    
    // from_bits_truncate: removes unknown bits
    let truncated = Color::from_bits_truncate(0b1001);
    println!("Truncated: {:?} (bits: {:03b})", truncated, truncated.bits());
    
    // from_bits_retain: preserves unknown bits
    let retained = Color::from_bits_retain(0b1001);
    println!("Retained: {:?} (bits: {:03b})", retained, retained.bits());
}

from_bits returns None for unknown bits; from_bits_truncate removes them; from_bits_retain keeps them.

Unknown Bits Behavior

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct Flags: u8 {
        const A = 0b0001;
        const B = 0b0010;
        const C = 0b0100;
    }
}
 
fn main() {
    // Value with unknown bits (0b1000)
    let raw_value: u8 = 0b1111;
    
    // from_bits rejects unknown bits
    match Flags::from_bits(raw_value) {
        Some(flags) => println!("from_bits: {:?}", flags),
        None => println!("from_bits: None (unknown bits present)"),
    }
    
    // from_bits_truncate removes unknown bits
    let truncated = Flags::from_bits_truncate(raw_value);
    println!("from_bits_truncate: {:?} = {:04b}", truncated, truncated.bits());
    
    // from_bits_retain preserves unknown bits
    let retained = Flags::from_bits_retain(raw_value);
    println!("from_bits_retain: {:?} = {:04b}", retained, retained.bits());
    
    // bits() returns all bits including unknown
    println!("Retained bits: {:04b}", retained.bits());
}

The three methods handle unknown bits differently: reject, truncate, or retain.

Round-Trip Preservation

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct Mode: u16 {
        const READ = 0b0001;
        const WRITE = 0b0010;
        const EXECUTE = 0b0100;
    }
}
 
fn round_trip_truncate(bits: u16) -> u16 {
    let flags = Mode::from_bits_truncate(bits);
    flags.bits()
}
 
fn round_trip_retain(bits: u16) -> u16 {
    let flags = Mode::from_bits_retain(bits);
    flags.bits()
}
 
fn main() {
    let original: u16 = 0b0111_1111; // Mix of known and unknown bits
    
    println!("Original: {:016b}", original);
    
    // Truncate loses unknown bits
    let truncated = round_trip_truncate(original);
    println!("After truncate: {:016b} (lost bits)", truncated);
    println!("Bits lost: {:016b}", original & !truncated);
    
    // Retain preserves all bits
    let retained = round_trip_retain(original);
    println!("After retain: {:016b} (preserved)", retained);
    println!("Identical: {}", original == retained);
}

from_bits_retain enables lossless round-trip conversion between flags and raw bits.

Interoperability with External Systems

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct FileMode: u32 {
        const READ = 0b0001;
        const WRITE = 0b0010;
        const EXECUTE = 0b0100;
    }
}
 
fn parse_file_mode_from_system(raw_mode: u32) -> FileMode {
    // External system may have bits we don't recognize
    // Use from_bits_retain to preserve them
    FileMode::from_bits_retain(raw_mode)
}
 
fn send_file_mode_to_system(mode: FileMode) -> u32 {
    // Send back all bits, including unknown ones
    mode.bits()
}
 
fn main() {
    // System returns mode with future bits
    let system_mode: u32 = 0b1111_0000_0111; // Unknown upper bits
    
    let parsed = parse_file_mode_from_system(system_mode);
    println!("Parsed flags: {:?}", parsed);
    println!("Parsed bits: {:032b}", parsed.bits());
    
    // Can still work with known bits
    println!("Has READ: {}", parsed.contains(FileMode::READ));
    println!("Has WRITE: {}", parsed.contains(FileMode::WRITE));
    
    // Send back preserves everything
    let returned = send_file_mode_to_system(parsed);
    println!("Returned: {:032b}", returned);
    println!("Preserved: {}", system_mode == returned);
}

from_bits_retain is essential when interoperating with systems that may define additional bits.

Bitwise Operations

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct Options: u8 {
        const A = 1 << 0;
        const B = 1 << 1;
        const C = 1 << 2;
    }
}
 
fn main() {
    // Start with known flags
    let opts = Options::A | Options::B;
    println!("Initial: {:?} = {:08b}", opts, opts.bits());
    
    // Set a bit that doesn't correspond to any flag
    let extended = opts.bits() | (1 << 7);
    let opts_with_unknown = Options::from_bits_retain(extended);
    
    println!("With unknown: {:?} = {:08b}", opts_with_unknown, opts_with_unknown.bits());
    
    // Still contains known flags
    println!("Has A: {}", opts_with_unknown.contains(Options::A));
    println!("Has B: {}", opts_with_unknown.contains(Options::B));
    println!("Has C: {}", opts_with_unknown.contains(Options::C));
    
    // Unknown bit preserved
    let unknown_bit = opts_with_unknown.bits() & !(Options::all().bits());
    println!("Unknown bits: {:08b}", unknown_bit);
}

Bitwise operations may set unknown bits; from_bits_retain preserves them.

Union and Intersection with Unknown Bits

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct Features: u8 {
        const FEATURE_A = 0b0001;
        const FEATURE_B = 0b0010;
    }
}
 
fn main() {
    // Create flags with unknown bits
    let with_unknown = Features::from_bits_retain(0b1001);
    
    // Union: combines all bits
    let combined = with_unknown.union(Features::FEATURE_B);
    println!("Union: {:?} = {:08b}", combined, combined.bits());
    
    // Intersection: keeps common bits
    let common = with_unknown.intersection(Features::all());
    println!("Intersection: {:?} = {:08b}", common, common.bits());
    
    // Difference: removes specified bits
    let diff = with_unknown.difference(Features::FEATURE_A);
    println!("Difference: {:?} = {:08b}", diff, diff.bits());
    
    // Symmetric difference: XOR
    let sym_diff = with_unknown.symmetric_difference(Features::all());
    println!("Sym diff: {:?} = {:08b}", sym_diff, sym_diff.bits());
}

Set operations work with unknown bits when using from_bits_retain.

Checking for Unknown Bits

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct Config: u8 {
        const DEBUG = 0b0001;
        const VERBOSE = 0b0010;
        const QUIET = 0b0100;
    }
}
 
fn has_unknown_bits(flags: Config) -> bool {
    // Check if any bits are not in the defined flags
    flags.bits() != flags.intersection(Config::all()).bits()
}
 
fn get_unknown_bits(flags: Config) -> u8 {
    // Get bits that don't correspond to any defined flag
    flags.bits() & !Config::all().bits()
}
 
fn main() {
    let known = Config::DEBUG | Config::VERBOSE;
    println!("Known: {:?}, unknown bits: {}", known, has_unknown_bits(known));
    
    let with_unknown = Config::from_bits_retain(0b1111);
    println!("With unknown: {:?}, unknown bits: {}", with_unknown, has_unknown_bits(with_unknown));
    
    let unknown_value = get_unknown_bits(with_unknown);
    println!("Unknown bits value: {:08b}", unknown_value);
    
    // is_all() only true if ALL bits are known
    println!("Is all: {}", with_unknown.is_all());
}

You can detect and extract unknown bits for validation or logging.

Validation Strategies

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct Permissions: u16 {
        const READ = 0b00000001;
        const WRITE = 0b00000010;
        const EXECUTE = 0b00000100;
    }
}
 
#[derive(Debug)]
enum ParseResult {
    Known(Permissions),
    Unknown { known: Permissions, unknown_bits: u16 },
}
 
fn parse_permissions(bits: u16) -> ParseResult {
    // Check for unknown bits
    let unknown = bits & !Permissions::all().bits();
    
    if unknown == 0 {
        ParseResult::Known(Permissions::from_bits_retain(bits))
    } else {
        ParseResult::Unknown {
            known: Permissions::from_bits_truncate(bits),
            unknown_bits: unknown,
        }
    }
}
 
fn main() {
    let known_bits: u16 = 0b0111;
    let unknown_bits: u16 = 0b1111_1111;
    
    match parse_permissions(known_bits) {
        ParseResult::Known(perm) => println!("Known: {:?}", perm),
        ParseResult::Unknown { known, unknown_bits } => {
            println!("Has unknown bits: {:016b}", unknown_bits);
            println!("Known part: {:?}", known);
        }
    }
    
    match parse_permissions(unknown_bits) {
        ParseResult::Known(perm) => println!("Known: {:?}", perm),
        ParseResult::Unknown { known, unknown_bits } => {
            println!("Has unknown bits: {:016b}", unknown_bits);
            println!("Known part: {:?}", known);
        }
    }
}

Different strategies for handling unknown bits depending on use case.

Practical Use Case: File Permissions

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct UnixPerms: u16 {
        // Owner
        const OWNER_READ =    0o400;
        const OWNER_WRITE =   0o200;
        const OWNER_EXEC =    0o100;
        // Group
        const GROUP_READ =    0o040;
        const GROUP_WRITE =   0o020;
        const GROUP_EXEC =    0o010;
        // Other
        const OTHER_READ =    0o004;
        const OTHER_WRITE =   0o002;
        const OTHER_EXEC =    0o001;
        // Special bits
        const SETUID =        0o4000;
        const SETGID =        0o2000;
        const STICKY =        0o1000;
    }
}
 
fn main() {
    // Unix file mode 0o755: rwxr-xr-x
    let mode_755 = UnixPerms::from_bits_retain(0o755);
    println!("Mode 755: {:o}", mode_755.bits());
    println!("Owner can read: {}", mode_755.contains(UnixPerms::OWNER_READ));
    println!("Owner can write: {}", mode_755.contains(UnixPerms::OWNER_WRITE));
    println!("Owner can execute: {}", mode_755.contains(UnixPerms::OWNER_EXEC));
    
    // File mode with future bits (hypothetical)
    let future_mode = 0o10755; // Has an extra bit we don't recognize
    let preserved = UnixPerms::from_bits_retain(future_mode);
    println!("Future mode preserved: {:o}", preserved.bits());
    
    // Truncate would lose information
    let truncated = UnixPerms::from_bits_truncate(future_mode);
    println!("Truncated mode: {:o}", truncated.bits());
    println!("Lost: {:o}", future_mode & !truncated.bits());
}

File permissions from external sources may contain bits not defined in your enum.

Practical Use Case: Hardware Registers

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct ControlRegister: u32 {
        const ENABLE = 1 << 0;
        const RESET = 1 << 1;
        const INTERRUPT_ENABLE = 1 << 2;
        const POWER_DOWN = 1 << 3;
        // Hardware version may have additional bits
    }
}
 
fn read_hardware_register() -> u32 {
    // Hardware might have vendor-specific bits
    0b1111_0000_1111 // Upper bits are vendor-specific
}
 
fn write_hardware_register(value: u32) {
    println!("Writing to hardware: {:032b}", value);
}
 
fn main() {
    // Read from hardware
    let raw_value = read_hardware_register();
    
    // Preserve all bits, work with known ones
    let register = ControlRegister::from_bits_retain(raw_value);
    
    // Check known bits
    if register.contains(ControlRegister::ENABLE) {
        println!("Device is enabled");
    }
    
    // Modify known bits
    let modified = register.union(ControlRegister::RESET);
    
    // Write back, preserving unknown bits
    write_hardware_register(modified.bits());
    
    // If we truncated, we'd lose vendor bits
    let truncated = ControlRegister::from_bits_truncate(raw_value);
    println!("Truncated loses: {:032b}", raw_value & !truncated.bits());
}

Hardware registers often contain vendor-specific bits that must be preserved.

Comparison Summary

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct Flags: u8 {
        const A = 0b01;
        const B = 0b10;
    }
}
 
fn main() {
    let known_bits: u8 = 0b11; // A | B
    let unknown_bits: u8 = 0b111; // A | B | unknown
    
    println!("Input: {:08b}", known_bits);
    println!("  from_bits: {:?}", Flags::from_bits(known_bits));
    println!("  from_bits_truncate: {:?}", Flags::from_bits_truncate(known_bits));
    println!("  from_bits_retain: {:?}", Flags::from_bits_retain(known_bits));
    
    println!("\nInput: {:08b}", unknown_bits);
    println!("  from_bits: {:?}", Flags::from_bits(unknown_bits));
    println!("  from_bits_truncate: {:?}", Flags::from_bits_truncate(unknown_bits));
    println!("  from_bits_retain: {:?}", Flags::from_bits_retain(unknown_bits));
    
    println!("\nUnknown bits with retain:");
    let retained = Flags::from_bits_retain(unknown_bits);
    println!("  bits(): {:08b}", retained.bits());
    println!("  all().bits(): {:08b}", Flags::all().bits());
    println!("  unknown bits: {:08b}", retained.bits() & !Flags::all().bits());
}

Serialization and Deserialization

use bitflags::Flags;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
bitflags::bitflags! {
    #[derive(Default)]
    struct Config: u8 {
        const FEATURE_A = 1 << 0;
        const FEATURE_B = 1 << 1;
        const FEATURE_C = 1 << 2;
    }
}
 
// When serializing/deserializing from external formats
fn deserialize_config(raw: u8, strict: bool) -> Result<Config, String> {
    if strict {
        // Strict mode: reject unknown bits
        Config::from_bits(raw)
            .ok_or_else(|| format!("Unknown bits in config: {:08b}", raw))
    } else {
        // Lenient mode: preserve unknown bits
        Ok(Config::from_bits_retain(raw))
    }
}
 
fn serialize_config(config: Config) -> u8 {
    // Always preserves all bits
    config.bits()
}
 
fn main() {
    let with_unknown: u8 = 0b1111;
    
    // Strict: fails on unknown bits
    match deserialize_config(with_unknown, true) {
        Ok(c) => println!("Strict: {:?}", c),
        Err(e) => println!("Strict error: {}", e),
    }
    
    // Lenient: preserves unknown bits
    match deserialize_config(with_unknown, false) {
        Ok(c) => println!("Lenient: {:?} = {:08b}", c, c.bits()),
        Err(e) => println!("Lenient error: {}", e),
    }
    
    // Round-trip with lenient deserialization
    let original = Config::from_bits_retain(with_unknown);
    let serialized = serialize_config(original);
    let deserialized = deserialize_config(serialized, false).unwrap();
    
    println!("Round-trip preserved: {}", original == deserialized);
    println!("Bits preserved: {:08b}", deserialized.bits());
}

Choose from_bits for strict validation, from_bits_retain for maximum compatibility.

Synthesis

Method comparison:

Method Unknown Bits Return Type Use Case
from_bits Reject Option<Flags> Strict validation
from_bits_truncate Remove Flags Known flags only
from_bits_retain Preserve Flags Interoperability
bits Return all Bits Raw value access

Bits access methods:

Method Description
bits() Returns raw bits (may include unknown)
all().bits() Returns bits for all defined flags
intersection(all).bits() Returns only known bits

When to use each method:

Scenario Method
Parsing external data (strict) from_bits
Parsing external data (preserving) from_bits_retain
Cleaning user input from_bits_truncate
Hardware interoperability from_bits_retain
Round-trip serialization from_bits_retain
Display/debug known flags bits() with validation

Key operations for unknown bits:

// Check if unknown bits present
fn has_unknown(flags: Flags) -> bool {
    flags.bits() != flags.intersection(Flags::all()).bits()
}
 
// Extract unknown bits
fn unknown_bits(flags: Flags) -> Bits {
    flags.bits() & !Flags::all().bits()
}
 
// Remove unknown bits
fn only_known(flags: Flags) -> Flags {
    Flags::from_bits_truncate(flags.bits())
}

Key insight: bits() and from_bits_retain() form a complementary pair for lossless bit manipulation—the from_bits_retain() constructor accepts any bits value including unknown bits, while bits() returns all bits including unknowns, enabling round-trip preservation. This is essential when interfacing with external systems (file formats, hardware registers, network protocols) that may define additional bits beyond your flag definitions. In contrast, from_bits() provides strict validation (rejecting unknown bits) and from_bits_truncate() sanitizes input (removing unknown bits), both of which are appropriate when you want to ensure only known flags are processed. The choice between these methods represents a fundamental trade-off: from_bits_retain preserves all information but may propagate unexpected bits, while from_bits and from_bits_truncate ensure known flags at the cost of information loss.