Loading page…
Rust walkthroughs
Loading page…
bitflags::bitflags! handle bitwise operations type-safety compared to raw integer types?The bitflags::bitflags! macro creates dedicated types for bit flag collections, enforcing type safety that raw integer types cannot provide. With raw integers, any value of the underlying type can be assigned, including invalid bit combinations or values outside the defined flags. The bitflags! macro generates a struct that wraps the integer while exposing only the defined flags as associated constants and restricting operations to meaningful combinations. The type system prevents mixing flags from different domains, and the generated API makes invalid states unrepresentable at the type level while still allowing efficient bitwise operations.
// Using raw integers for flags - no type safety
const READABLE: u32 = 0b001;
const WRITABLE: u32 = 0b010;
const EXECUTABLE: u32 = 0b100;
fn check_permissions(perms: u32) -> bool {
(perms & READABLE) != 0
}
fn main() {
// Works correctly
check_permissions(READABLE);
check_permissions(READABLE | WRITABLE);
// But this also compiles - complete nonsense
check_permissions(999);
check_permissions(0xFFFFFFFF);
// Can mix flags from different domains
const FILE_HIDDEN: u32 = 0b1000;
const NETWORK_PORT: u32 = 0b0001;
// This compiles but makes no sense
let mixed = FILE_HIDDEN | NETWORK_PORT;
}Raw integers accept any value, including meaningless combinations.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Permissions: u32 {
const READABLE = 0b001;
const WRITABLE = 0b010;
const EXECUTABLE = 0b100;
}
}
fn check_permissions(perms: Permissions) -> bool {
perms.contains(Permissions::READABLE)
}
fn main() {
// Correct usage
check_permissions(Permissions::READABLE);
check_permissions(Permissions::READABLE | Permissions::WRITABLE);
// This doesn't compile - type mismatch
// check_permissions(999);
// check_permissions(0xFFFFFFFF);
// Type-safe combinations
let all = Permissions::all();
let none = Permissions::empty();
let read_write = Permissions::READABLE | Permissions::WRITABLE;
println!("{:?}", read_write); // Permissions(READABLE | WRITABLE)
}bitflags! creates a distinct type that only accepts defined flags.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct FileFlags: u32 {
const READ = 0b001;
const WRITE = 0b010;
const EXECUTE = 0b100;
}
#[derive(Debug, Clone, Copy)]
struct NetworkFlags: u32 {
const TCP = 0b001;
const UDP = 0b010;
const TLS = 0b100;
}
}
fn main() {
let file_perms = FileFlags::READ | FileFlags::WRITE;
let net_flags = NetworkFlags::TCP | NetworkFlags::TLS;
// This compiles - same type
let more_file: FileFlags = file_perms | FileFlags::EXECUTE;
// This doesn't compile - different types
// let invalid = file_perms | NetworkFlags::TCP;
// This doesn't compile - wrong parameter type
// fn process_file(f: FileFlags) {}
// process_file(NetworkFlags::TCP);
}Different flag types cannot be mixed, preventing domain confusion.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Access: u8 {
const READ = 0b001;
const WRITE = 0b010;
const EXECUTE = 0b100;
}
}
fn main() {
let read = Access::READ;
let write = Access::WRITE;
// OR - combine flags
let read_write = read | write;
assert_eq!(read_write, Access::READ | Access::WRITE);
// AND - intersect flags
let intersection = read_write & Access::READ;
assert_eq!(intersection, Access::READ);
// XOR - toggle flags
let toggled = read_write ^ Access::READ;
assert_eq!(toggled, Access::WRITE);
// NOT - invert flags (within defined bits)
let inverted = !Access::READ;
println!("Inverted: {:?}", inverted);
// All operations return the same type - type safety preserved
let combined = (read | write) & Access::READ;
assert_eq!(combined, Access::READ);
}Bitwise operations return the flag type, maintaining type safety.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Status: u32 {
const ACTIVE = 0b001;
const VISIBLE = 0b010;
const ENABLED = 0b100;
}
}
fn main() {
let mut status = Status::ACTIVE;
// contains - check if flag is set
assert!(status.contains(Status::ACTIVE));
assert!(!status.contains(Status::VISIBLE));
// insert - set a flag
status.insert(Status::VISIBLE);
assert!(status.contains(Status::VISIBLE));
// remove - clear a flag
status.remove(Status::ACTIVE);
assert!(!status.contains(Status::ACTIVE));
// toggle - flip a flag
status.toggle(Status::ENABLED);
assert!(status.contains(Status::ENABLED));
status.toggle(Status::ENABLED);
assert!(!status.contains(Status::ENABLED));
// set - conditionally set or clear
status.set(Status::ACTIVE, true);
assert!(status.contains(Status::ACTIVE));
status.set(Status::ACTIVE, false);
assert!(!status.contains(Status::ACTIVE));
}Named methods provide clear, type-safe flag manipulation.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Capabilities: u8 {
const FAST = 0b001;
const CHEAP = 0b010;
const GOOD = 0b100;
}
}
fn main() {
let caps1 = Capabilities::FAST | Capabilities::CHEAP;
let caps2 = Capabilities::CHEAP | Capabilities::GOOD;
// union - all flags from either
let all = caps1.union(caps2);
assert_eq!(all, Capabilities::all());
// intersection - flags in both
let common = caps1.intersection(caps2);
assert_eq!(common, Capabilities::CHEAP);
// difference - flags in self but not other
let unique = caps1.difference(caps2);
assert_eq!(unique, Capabilities::FAST);
// symmetric_difference - flags in one but not both
let xor = caps1.symmetric_difference(caps2);
assert_eq!(xor, Capabilities::FAST | Capabilities::GOOD);
// complement - flags not set
let not_set = caps1.complement();
assert_eq!(not_set, Capabilities::GOOD);
}Set operations work on the flag type, returning the same type.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Mode: u8 {
const READ = 1 << 0;
const WRITE = 1 << 1;
const EXECUTE = 1 << 2;
}
}
fn main() {
let read_write = Mode::READ | Mode::WRITE;
let read = Mode::READ;
// is_empty - no flags set
assert!(!read_write.is_empty());
assert!(Mode::empty().is_empty());
// is_all - all defined flags set
assert!(!read_write.is_all());
assert!(Mode::all().is_all());
// intersects - any common flags
assert!(read_write.intersects(read));
assert!(!read_write.intersects(Mode::EXECUTE));
// contains - all specified flags are set
assert!(read_write.contains(read));
assert!(!read_write.contains(Mode::EXECUTE));
// Equality comparison
assert_eq!(read_write, Mode::READ | Mode::WRITE);
assert_ne!(read_write, read);
}Query methods provide type-safe ways to check flag states.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct Options: u8 {
const A = 0b0001;
const B = 0b0010;
const C = 0b0100;
// Note: bit 3 (0b1000) is not defined
}
}
fn main() {
// Cannot create from raw integer directly (by default)
// let invalid = Options::from_bits(0b1000); // Returns None
// from_bits returns Option - safe construction
let valid = Options::from_bits(0b0011);
assert!(valid.is_some()); // A | B
assert_eq!(valid.unwrap(), Options::A | Options::B);
// Undefined bits result in None
let invalid = Options::from_bits(0b1000);
assert!(invalid.is_none());
// from_bits_truncate removes undefined bits
let truncated = Options::from_bits_truncate(0b1011);
assert_eq!(truncated, Options::A | Options::B); // 0b1000 removed
// from_bits_unchecked - unsafe, bypasses checks
// Only use if you're certain the value is valid
unsafe {
let unchecked = Options::from_bits_unchecked(0b1011);
println!("Unchecked: {:?}", unchecked);
}
}Construction from raw values requires explicit validation.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Flags: u32 {
const FEATURE_A = 1;
const FEATURE_B = 2;
const FEATURE_C = 4;
}
}
fn process_flags(flags: Flags) {
println!("Processing: {:?}", flags);
}
fn main() {
// Safe construction
let flags = Flags::FEATURE_A | Flags::FEATURE_B;
process_flags(flags);
// Parsing from integer - must validate
fn parse_flags(value: u32) -> Option<Flags> {
Flags::from_bits(value)
}
// Valid value
assert!(parse_flags(3).is_some()); // A | B
// Invalid value - undefined bit
assert!(parse_flags(8).is_none());
// This wouldn't compile - can't pass raw integer
// process_flags(3);
}Functions accepting flag types cannot receive raw integers.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Permissions: u32 {
// Named constants
const NONE = 0;
const READ = 1 << 0;
const WRITE = 1 << 1;
const EXECUTE = 1 << 2;
// Combined constants
const READ_WRITE = Self::READ.bits() | Self::WRITE.bits();
const ALL = Self::READ.bits() | Self::WRITE.bits() | Self::EXECUTE.bits();
}
}
fn main() {
// Pre-defined combinations
let rw = Permissions::READ_WRITE;
assert_eq!(rw, Permissions::READ | Permissions::WRITE);
// all() and empty() are automatically provided
let everything = Permissions::all();
let nothing = Permissions::empty();
assert_eq!(everything, Permissions::ALL);
assert_eq!(nothing, Permissions::NONE);
// bits() gives the raw value
assert_eq!(Permissions::READ.bits(), 1);
assert_eq!(rw.bits(), 3);
}Define common combinations as named constants for clarity.
use bitflags::bitflags;
// System-level flags (e.g., from a C library)
mod sys {
pub const O_RDONLY: i32 = 0;
pub const O_WRONLY: i32 = 1;
pub const O_RDWR: i32 = 2;
pub const O_CREAT: i32 = 64;
pub const O_TRUNC: i32 = 512;
pub const O_APPEND: i32 = 1024;
}
bitflags! {
#[derive(Debug, Clone, Copy)]
struct OpenFlags: i32 {
const RDONLY = sys::O_RDONLY;
const WRONLY = sys::O_WRONLY;
const RDWR = sys::O_RDWR;
const CREAT = sys::O_CREAT;
const TRUNC = sys::O_TRUNC;
const APPEND = sys::O_APPEND;
}
}
fn open_file(path: &str, flags: OpenFlags) -> std::io::Result<()> {
// Safe access to raw value for FFI
let raw_flags = flags.bits();
println!("Opening {} with flags {}", path, raw_flags);
// unsafe { libc::open(path.as_ptr(), raw_flags) }
Ok(())
}
fn main() {
let flags = OpenFlags::WRONLY | OpenFlags::CREAT | OpenFlags::TRUNC;
open_file("file.txt", flags).unwrap();
// Can't pass wrong flags
// open_file("file.txt", 64); // Type error
}Wrap external constants in type-safe flag types for FFI integration.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct SafeFlags: u8 {
const A = 1;
const B = 2;
const C = 4;
}
}
// Raw integer approach
mod raw {
pub const A: u8 = 1;
pub const B: u8 = 2;
pub const C: u8 = 4;
}
fn compare_approaches() {
// === Type Safety ===
// bitflags: compile-time type checking
let safe = SafeFlags::A | SafeFlags::B;
// raw: any u8 is valid
let unsafe_val: u8 = 255; // No flags defined, but accepted
let nonsense: u8 = raw::A | 128; // Mixes defined and undefined
// === Self-Documentation ===
// bitflags: type name carries meaning
fn with_safe(flags: SafeFlags) {
println!("Flags: {:?}", flags);
}
// raw: type doesn't indicate purpose
fn with_raw(flags: u8) {
println!("Flags: {}", flags);
}
// === Debugging ===
println!("Safe: {:?}", safe); // Prints: SafeFlags(A | B)
println!("Raw: {}", unsafe_val); // Prints: 255 (meaning unclear)
// === API Safety ===
// bitflags: contains() is clear
if safe.contains(SafeFlags::A) {
println!("A is set");
}
// raw: bit operations are error-prone
if (unsafe_val & raw::A) != 0 {
println!("Maybe A is set?");
}
}bitflags! provides type safety, clarity, and self-documentation.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct State: u8 {
const STARTED = 1;
const PAUSED = 2;
const STOPPED = 4;
}
}
fn main() {
let state = State::STARTED | State::PAUSED;
// Convert to bits for storage/serialization
let bits = state.bits();
println!("Serialized: {}", bits);
// Parse from bits with validation
let restored = State::from_bits(bits);
assert_eq!(restored, Some(state));
// Parse with truncation (removes undefined bits)
let truncated = State::from_bits_truncate(0xFF);
assert_eq!(truncated, State::all());
// Can also check validity
if State::is_valid_bit(0b111) {
println!("Bits 0b111 are valid for State");
}
}Safe conversion to/from raw bits supports serialization.
use bitflags::bitflags;
bitflags! {
// Can derive standard traits
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Config: u32 {
const DEBUG = 1;
const VERBOSE = 2;
const LOG_FILE = 4;
}
}
impl Config {
// Can add custom methods
pub fn is_debug_mode(&self) -> bool {
self.contains(Self::DEBUG) && self.contains(Self::VERBOSE)
}
pub fn enable_logging(&mut self) {
self.insert(Self::DEBUG | Self::LOG_FILE);
}
}
fn main() {
let mut config = Config::DEBUG;
config.enable_logging();
if config.is_debug_mode() {
println!("Debug mode enabled");
}
// Hash and Eq work correctly
let mut seen = std::collections::HashSet::new();
seen.insert(config);
}Extend flag types with custom methods and derive additional traits.
| Feature | Raw Integers | bitflags! |
|---------|--------------|-------------|
| Type safety | None | Full |
| Mixing domains | Allowed (wrong) | Compile error |
| Invalid values | Accepted | Rejected or truncated |
| Self-documenting | No | Yes (Debug impl) |
| Named constants | Manual | Automatic |
| Set operations | Manual bit ops | Named methods |
| Parse validation | Manual | Built-in |
| Default values | None | empty(), all() |
bitflags::bitflags! transforms raw bitwise operations into a type-safe API without sacrificing efficiency:
Type Safety Benefits:
API Clarity:
contains, insert, remove) replace error-prone bit operationsDebug implementation shows flag names instead of raw numbersPerformance:
Copy trait allows efficient value semanticsKey insight: The bitflags! macro demonstrates how Rust's type system can encode constraints that would otherwise be runtime checks in other languages. By wrapping the integer in a struct and controlling construction, the impossible states become unrepresentable at compile time. The generated API guides developers toward correct usage while preventing entire categories of bugs that plague raw integer flag implementations.