How does bitflags::Flags::intersection enable safe bitwise AND operations on flag sets?

bitflags::Flags::intersection provides a type-safe wrapper around bitwise AND operations that ensures only valid flag combinations are produced, preventing the creation of meaningless bit patterns that could occur with raw bitwise operations. When you use intersection, the result is guaranteed to be a valid flags value—containing only bits that are defined in your flags type—because the bitflags! macro generates a type that cannot represent invalid states. This differs fundamentally from raw bitwise AND on integers: if you accidentally AND two flag values represented as u32, you might get a bit pattern that doesn't correspond to any defined flag or combination, leading to subtle bugs where invalid flag states propagate through your system. intersection also integrates with the type system to prevent mixing flags from different types, catching category errors at compile time that would be silent bugs with raw integers.

Defining Flags with bitflags

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Permissions: u32 {
        const READ = 0b0001;
        const WRITE = 0b0010;
        const EXECUTE = 0b0100;
        const ADMIN = 0b1000;
    }
}
 
fn main() {
    // Create a flags value with multiple bits set
    let user_perms = Permissions::READ | Permissions::WRITE;
    println!("User permissions: {:?}", user_perms);
    
    // Each constant is a Permissions value, not a raw integer
    // The type system ensures we can't mix with other flag types
}

The bitflags! macro creates a type-safe wrapper around the underlying integer.

Using intersection for Safe AND Operations

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 full_access = Permissions::READ | Permissions::WRITE | Permissions::EXECUTE;
    let read_only = Permissions::READ;
    
    // intersection returns only bits set in BOTH values
    let common = full_access.intersection(read_only);
    println!("Common permissions: {:?}", common);
    // Permissions(READ)
    
    // Equivalent to bitwise AND but type-safe
    assert_eq!(common, full_access & read_only);
    
    // The result is always a valid Permissions value
    // No invalid bit patterns can be created
}

intersection computes the bitwise AND while maintaining type safety.

The Safety Difference from Raw Bitwise Operations

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Flags: u8 {
        const A = 0b0001;
        const B = 0b0010;
        const C = 0b0100;
    }
}
 
fn main() {
    // With raw integers, invalid bits can appear
    let raw_a: u8 = 0b0001;
    let raw_invalid: u8 = 0b1000;  // Bit not defined in Flags!
    
    let raw_result = raw_a & raw_invalid;
    println!("Raw AND result: {:04b}", raw_result);  // 0000 - okay here
    
    // But what about undefined bits?
    let raw_mystery: u8 = 0b1111;  // All bits set, some undefined
    let raw_weird = raw_a & raw_mystery;
    println!("Raw AND with mystery: {:04b}", raw_weird);  // 0001
    
    // With bitflags, undefined bits cannot be set in the first place
    let flags_a = Flags::A;
    // Cannot create Flags with undefined bits directly
    // This would fail to compile if we tried
    
    // intersection always produces valid Flags
    let other = Flags::B | Flags::C;
    let safe = flags_a.intersection(other);
    println!("Safe intersection: {:?}", safe);  // empty flags
}

Raw integers can hold undefined bits; bitflags types cannot.

Intersection Versus Bitwise AND Operator

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Access: u32 {
        const READ = 1 << 0;
        const WRITE = 1 << 1;
        const DELETE = 1 << 2;
    }
}
 
fn main() {
    let read_write = Access::READ | Access::WRITE;
    let write_delete = Access::WRITE | Access::DELETE;
    
    // Two equivalent ways to compute intersection:
    
    // Method 1: Method call
    let result1 = read_write.intersection(write_delete);
    println!("intersection(): {:?}", result1);  // WRITE
    
    // Method 2: Bitwise AND operator
    let result2 = read_write & write_delete;
    println!("& operator: {:?}", result2);  // WRITE
    
    // They produce identical results
    assert_eq!(result1, result2);
    
    // Use intersection() for explicit, readable code
    // Use & for concise expressions in complex operations
}

Both intersection() and & produce the same result; choose based on readability.

Checking for Flag Presence with intersection

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Capabilities: u32 {
        const CAN_READ = 1 << 0;
        const CAN_WRITE = 1 << 1;
        const CAN_DELETE = 1 << 2;
        const CAN_SHARE = 1 << 3;
    }
}
 
fn main() {
    let user_caps = Capabilities::CAN_READ | Capabilities::CAN_WRITE;
    
    // Check if user has READ capability
    let has_read = user_caps.intersection(Capabilities::CAN_READ);
    if has_read == Capabilities::CAN_READ {
        println!("User can read");
    }
    
    // More idiomatically, use contains() which is clearer
    if user_caps.contains(Capabilities::CAN_READ) {
        println!("User can read (using contains)");
    }
    
    // intersection() is useful when you want the actual intersection value
    // rather than just checking presence
    
    let required = Capabilities::CAN_READ | Capabilities::CAN_WRITE;
    let missing = required.difference(user_caps);
    println!("Missing capabilities: {:?}", missing);
}

intersection returns the actual overlap; contains checks for presence.

Intersection with Empty Results

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Status: u8 {
        const ACTIVE = 1 << 0;
        const PENDING = 1 << 1;
        const ARCHIVED = 1 << 2;
    }
}
 
fn main() {
    let active_status = Status::ACTIVE;
    let archived_status = Status::ARCHIVED;
    
    // No common bits
    let result = active_status.intersection(archived_status);
    println!("Intersection of ACTIVE and ARCHIVED: {:?}", result);
    // Empty flags - no bits set
    
    // Check if result is empty
    if result.is_empty() {
        println!("No overlapping status bits");
    }
    
    // Empty flags are still valid - they're a valid Flags value
    // This is different from "undefined" bits
    assert!(result.is_empty());
    assert!(result.bits() == 0);
}

Empty flags are valid—they represent "no flags set."

Type Safety Across Different Flag Types

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct FilePermissions: u8 {
        const READ = 1 << 0;
        const WRITE = 1 << 1;
    }
    
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct NetworkPermissions: u8 {
        const CONNECT = 1 << 0;
        const LISTEN = 1 << 1;
    }
}
 
fn main() {
    let file_perms = FilePermissions::READ | FilePermissions::WRITE;
    let net_perms = NetworkPermissions::CONNECT | NetworkPermissions::LISTEN;
    
    // This would NOT compile - type mismatch:
    // let result = file_perms.intersection(net_perms);
    // error: expected FilePermissions, found NetworkPermissions
    
    // With raw integers, this would silently succeed:
    let file_raw: u8 = 0b11;
    let net_raw: u8 = 0b11;
    let mixed = file_raw & net_raw;  // "Works" but meaningless
    
    // bitflags prevents mixing different flag categories
    println!("Type safety prevents category errors");
}

The type system prevents mixing flags from different categories.

Combining Intersection with Other Operations

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Features: u32 {
        const FEATURE_A = 1 << 0;
        const FEATURE_B = 1 << 1;
        const FEATURE_C = 1 << 2;
        const FEATURE_D = 1 << 3;
        const ALL = Self::FEATURE_A.bits() | Self::FEATURE_B.bits() 
                  | Self::FEATURE_C.bits() | Self::FEATURE_D.bits();
    }
}
 
fn main() {
    let enabled = Features::FEATURE_A | Features::FEATURE_C;
    let requested = Features::FEATURE_A | Features::FEATURE_B | Features::FEATURE_D;
    
    // Find which requested features are actually enabled
    let available = enabled.intersection(requested);
    println!("Available features: {:?}", available);
    // FEATURE_A
    
    // Find which requested features are NOT enabled
    let unavailable = requested.difference(enabled);
    println!("Unavailable features: {:?}", unavailable);
    // FEATURE_B | FEATURE_D
    
    // Check if any requested features are available
    let has_any = enabled.intersects(requested);
    println!("Has any requested: {}", has_any);
    
    // Check if ALL requested features are available
    let has_all = enabled.contains(requested);
    println!("Has all requested: {}", has_all);
}

Combine intersection with difference, intersects, and contains for complex logic.

Practical Example: Permission Filtering

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Permission: u32 {
        const READ = 1 << 0;
        const WRITE = 1 << 1;
        const DELETE = 1 << 2;
        const ADMIN = 1 << 3;
    }
}
 
#[derive(Debug)]
struct User {
    name: String,
    permissions: Permission,
}
 
impl User {
    fn can_perform(&self, required: Permission) -> bool {
        // Check if user has ALL required permissions
        self.permissions.contains(required)
    }
    
    fn filter_permissions(&self, filter: Permission) -> Permission {
        // Return only permissions that match the filter
        self.permissions.intersection(filter)
    }
}
 
fn main() {
    let admin = User {
        name: "Admin".to_string(),
        permissions: Permission::READ | Permission::WRITE | Permission::DELETE | Permission::ADMIN,
    };
    
    let editor = User {
        name: "Editor".to_string(),
        permissions: Permission::READ | Permission::WRITE,
    };
    
    // What permissions does admin have that editor doesn't?
    let admin_only = admin.permissions.difference(editor.permissions);
    println!("Admin-only permissions: {:?}", admin_only);
    // DELETE | ADMIN
    
    // What permissions do both have?
    let shared = admin.permissions.intersection(editor.permissions);
    println!("Shared permissions: {:?}", shared);
    // READ | WRITE
    
    // Filter to read/write only
    let rw_only = admin.filter_permissions(Permission::READ | Permission::WRITE);
    println!("Filtered to R/W: {:?}", rw_only);
}

intersection helps filter and compare permission sets safely.

The Flags Trait Methods

use bitflags::bitflags;
 
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Mode: u8 {
        const READ = 1 << 0;
        const WRITE = 1 << 1;
        const EXEC = 1 << 2;
    }
}
 
fn main() {
    let mode = Mode::READ | Mode::WRITE;
    
    // Core Flags trait methods:
    
    // intersection - bits in both (AND)
    let and = mode.intersection(Mode::READ);
    println!("intersection: {:?}", and);
    
    // union - bits in either (OR)
    let or = mode.union(Mode::EXEC);
    println!("union: {:?}", or);
    
    // difference - bits in self but not other (AND NOT)
    let diff = mode.difference(Mode::WRITE);
    println!("difference: {:?}", diff);
    
    // symmetric_difference - bits in either but not both (XOR)
    let xor = mode.symmetric_difference(Mode::WRITE | Mode::EXEC);
    println!("symmetric_difference: {:?}", xor);
    
    // complement - all bits NOT set (NOT)
    let not = mode.complement();
    println!("complement: {:?}", not);
    
    // Query methods:
    println!("contains READ: {}", mode.contains(Mode::READ));
    println!("intersects WRITE: {}", mode.intersects(Mode::WRITE));
    println!("is_empty: {}", mode.is_empty());
    println!("is_all: {}", mode.is_all());
}

The Flags trait provides a complete set of type-safe bitwise operations.

Synthesis

Operation mappings:

Method Operator Operation Result
intersection & AND Bits in both values
union | OR Bits in either value
difference - or & ! AND NOT Bits in self, not other
symmetric_difference ^ XOR Bits in exactly one value
complement ! NOT All bits not in value

Safety guarantees:

Aspect Raw bitwise bitflags::Flags
Invalid bits Can appear Cannot be created
Type mixing Silent bugs Compile error
Empty result Valid integer Valid empty flags
Result type Same integer type Same flags type

Key insight: bitflags::Flags::intersection and related methods transform bitwise operations from "operations on integers that happen to represent flags" into "operations on flag types that maintain invariants." The safety benefit is twofold: preventing undefined bit patterns and preventing category confusion. Undefined bit patterns—like having bit 7 set when only bits 0-3 are defined—cannot occur because bitflags only exposes constructors for valid flag combinations. Category confusion—ANDing file permissions with network permissions—cannot occur because each flag type is a distinct type that the compiler tracks separately. These guarantees compound: when you see Permissions in your function signature, you know it contains only valid permission bits, was constructed through proper means, and will be combined only with other Permissions values. Raw integers carrying flag semantics offer none of these guarantees; a u32 could come from anywhere, contain any bits, and be combined with any other u32. The intersection method is a manifestation of this broader principle: a simple bitwise AND wrapped in type safety that propagates correctness throughout the codebase.