Loading pageā¦
Rust walkthroughs
Loading pageā¦
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.
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.
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.
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.
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.
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.
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."
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.
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.
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.
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.
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.