Loading pageâŚ
Rust walkthroughs
Loading pageâŚ
bitflags::Flags trait enable runtime inspection of flag values compared to using raw bitwise operations?The bitflags::Flags trait provides a standardized interface for runtime inspection of bit flag values, including iteration over set flags, conversion to/from names, and validation of known bits. Raw bitwise operations like &, |, and ^ work at the bit level without any semantic awarenessâthey manipulate bits without knowing what those bits represent. The Flags trait bridges this gap by associating meaning with bits, allowing you to enumerate which flags are set, check if a value contains unknown bits, and serialize flags to human-readable names. This transforms bit manipulation from opaque arithmetic into self-documenting operations where you can query and display flag states at runtime.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct Permissions: u32 {
const READ = 0b0001;
const WRITE = 0b0010;
const EXECUTE = 0b0100;
const ADMIN = 0b1000;
}
}
fn main() {
// Creating flags with bitwise OR
let perms = Permissions::READ | Permissions::WRITE;
println!("Permissions: {:?}", perms);
// Raw bitwise operations work
let can_execute = perms.contains(Permissions::EXECUTE);
println!("Can execute: {}", can_execute);
}The bitflags! macro creates a type-safe wrapper around bitwise operations.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct Flags: u8 {
const A = 0b0001;
const B = 0b0010;
const C = 0b0100;
}
}
fn main() {
let flags = Flags::A | Flags::C;
// Flags trait provides: iter(), names(), from_bits(), from_name()
// iter() - enumerate all set flags
println!("Set flags:");
for flag in flags.iter() {
println!(" {:?}", flag);
}
// from_bits() - convert raw bits to flags
let from_bits = Flags::from_bits(0b0101);
match from_bits {
Some(f) => println!("From bits: {:?}", f),
None => println!("Invalid bits"),
}
// from_name() - convert string name to flag
let from_name = Flags::from_name("A");
match from_name {
Some(f) => println!("From name 'A': {:?}", f),
None => println!("Unknown name"),
}
}The Flags trait enables conversion between different representations.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct Status: u8 {
const ACTIVE = 0b0001;
const PENDING = 0b0010;
const VERIFIED = 0b0100;
const SUSPENDED = 0b1000;
}
}
fn raw_bitwise_approach(value: u8) {
println!("Raw bitwise approach:");
// Checking individual bits manually
let active = value & 0b0001 != 0;
let pending = value & 0b0010 != 0;
let verified = value & 0b0100 != 0;
println!(" Active: {}, Pending: {}, Verified: {}",
active, pending, verified);
// Listing all set bits requires manual mapping
let names = ["ACTIVE", "PENDING", "VERIFIED", "SUSPENDED"];
let bits = [0b0001, 0b0010, 0b0100, 0b1000];
print!(" Set: ");
for (name, bit) in names.iter().zip(bits.iter()) {
if value & bit != 0 {
print!("{} ", name);
}
}
println!();
// Unknown bits require checking against known mask
let known_mask = 0b1111;
let unknown = value & !known_mask;
if unknown != 0 {
println!(" Unknown bits: {:b}", unknown);
}
}
fn flags_trait_approach(value: Status) {
println!("Flags trait approach:");
// Contains check is semantic
println!(" Active: {}", value.contains(Status::ACTIVE));
// Iterating over set flags is built-in
print!(" Set: ");
for flag in value.iter() {
print!("{:?} ", flag);
}
println!();
// Unknown bits detection is built-in
println!(" Has unknown bits: {}", !value.is_empty() && value.bits() != 0);
}
fn main() {
let raw_value: u8 = 0b0101;
raw_bitwise_approach(raw_value);
// Convert to flags type
let flags = Status::from_bits_truncate(raw_value);
flags_trait_approach(flags);
}The Flags trait provides semantic operations instead of raw bit manipulation.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct Capabilities: u16 {
const FEATURE_A = 1 << 0;
const FEATURE_B = 1 << 1;
const FEATURE_C = 1 << 2;
const FEATURE_D = 1 << 3;
const FEATURE_E = 1 << 4;
}
}
fn main() {
let caps = Capabilities::FEATURE_A | Capabilities::FEATURE_C | Capabilities::FEATURE_E;
// Iterate over all set flags
println!("Enabled capabilities:");
for flag in caps.iter() {
println!(" {:?}", flag);
}
// Count flags
let count = caps.iter().count();
println!("Total flags set: {}", count);
// Collect into a vector
let all_flags: Vec<_> = caps.iter().collect();
println!("All flags: {:?}", all_flags);
}.iter() yields each set flag as a typed value.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct Options: u8 {
const VERBOSE = 0b001;
const QUIET = 0b010;
const DEBUG = 0b100;
}
}
fn main() {
// Convert name to flag value
let verbose = Options::from_name("VERBOSE");
assert_eq!(verbose, Some(Options::VERBOSE));
let unknown = Options::from_name("UNKNOWN");
assert_eq!(unknown, None);
// Convert flags to names
let flags = Options::VERBOSE | Options::DEBUG;
let names: Vec<&str> = flags.iter_names().map(|(name, _)| name).collect();
println!("Flag names: {:?}", names);
// Useful for configuration parsing
fn parse_options(names: &[&str]) -> Result<Options, String> {
let mut flags = Options::empty();
for name in names {
match Options::from_name(name) {
Some(flag) => flags |= flag,
None => return Err(format!("Unknown option: {}", name)),
}
}
Ok(flags)
}
let opts = parse_options(&["VERBOSE", "DEBUG"]).unwrap();
println!("Parsed options: {:?}", opts);
}.from_name() and .iter_names() enable string-based flag handling.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct Mode: u8 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
fn main() {
// Valid flags
let valid = Mode::from_bits(0b0111);
println!("Valid: {:?}", valid); // Some(Mode::all())
// Invalid bits (contains unknown bits)
let invalid = Mode::from_bits(0b1111);
println!("Invalid: {:?}", invalid); // None (unknown bit 0b1000)
// Truncate unknown bits
let truncated = Mode::from_bits_truncate(0b1111);
println!("Truncated: {:?} (bits: {:b})", truncated, truncated.bits());
// Check if all bits are known
let unknown_bits = Mode::from_bits(0b1111);
match unknown_bits {
Some(_) => println!("All bits recognized"),
None => println!("Contains unknown bits"),
}
// Difference between from_bits and from_bits_truncate
let raw: u8 = 0b11111; // Has extra bits beyond READ|WRITE|EXECUTE
// from_bits returns None if unknown bits present
assert!(Mode::from_bits(raw).is_none());
// from_bits_truncate removes unknown bits
let truncated = Mode::from_bits_truncate(raw);
assert_eq!(truncated, Mode::READ | Mode::WRITE | Mode::EXECUTE);
}from_bits validates, from_bits_truncate strips unknown bits.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct FilePermissions: u16 {
const OWNER_READ = 0b0000000001;
const OWNER_WRITE = 0b0000000010;
const OWNER_EXEC = 0b0000000100;
const GROUP_READ = 0b0000001000;
const GROUP_WRITE = 0b0000010000;
const GROUP_EXEC = 0b0000100000;
const OTHER_READ = 0b0001000000;
const OTHER_WRITE = 0b0010000000;
const OTHER_EXEC = 0b0100000000;
}
}
fn inspect_permissions(perm: FilePermissions) {
println!("Permission bits: {:09b}", perm.bits());
// Iterate and describe each set permission
let descriptions = [
("OWNER_READ", "Owner can read"),
("OWNER_WRITE", "Owner can write"),
("OWNER_EXEC", "Owner can execute"),
("GROUP_READ", "Group can read"),
("GROUP_WRITE", "Group can write"),
("GROUP_EXEC", "Group can execute"),
("OTHER_READ", "Others can read"),
("OTHER_WRITE", "Others can write"),
("OTHER_EXEC", "Others can execute"),
];
for (name, desc) in descriptions {
if let Some(flag) = FilePermissions::from_name(name) {
if perm.contains(flag) {
println!(" {}", desc);
}
}
}
// Check for unknown bits
let known_mask = FilePermissions::all().bits();
let unknown = perm.bits() & !known_mask;
if unknown != 0 {
println!(" WARNING: Unknown bits {:b}", unknown);
}
}
fn main() {
let my_perms = FilePermissions::OWNER_READ | FilePermissions::OWNER_WRITE
| FilePermissions::GROUP_READ;
inspect_permissions(my_perms);
// With unknown bits
let raw_perms = FilePermissions::from_bits_truncate(0b1111111111);
inspect_permissions(raw_perms);
}The Flags trait enables rich runtime inspection without manual bit manipulation.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct State: u8 {
const INITIALIZED = 1;
const RUNNING = 2;
const PAUSED = 4;
const STOPPED = 8;
}
}
fn main() {
let state = State::INITIALIZED | State::RUNNING;
// To human-readable string
let names: Vec<&str> = state.iter_names().map(|(name, _)| name).collect();
println!("State: {}", names.join(" | "));
// From string representation
let restored = parse_state("INITIALIZED | RUNNING");
println!("Restored: {:?}", restored);
// To bits (for binary protocols)
let bits = state.bits();
println!("Bits: {:b}", bits);
// From bits (for binary protocols)
let from_bits = State::from_bits(bits);
println!("From bits: {:?}", from_bits);
}
fn parse_state(s: &str) -> State {
let mut state = State::empty();
for part in s.split(" | ") {
if let Some(flag) = State::from_name(part.trim()) {
state |= flag;
}
}
state
}The Flags trait supports serialization to/from multiple formats.
// Raw enum approach (no runtime inspection)
#[derive(Debug, Clone, Copy)]
enum RawFlag {
A = 0b001,
B = 0b010,
C = 0b100,
}
impl RawFlag {
fn mask(&self) -> u8 {
*self as u8
}
}
// Combining raw flags is awkward
fn has_flag_raw(value: u8, flag: RawFlag) -> bool {
value & flag.mask() != 0
}
fn list_flags_raw(value: u8) -> Vec<&'static str> {
let mut names = vec![];
if value & RawFlag::A as u8 != 0 { names.push("A"); }
if value & RawFlag::B as u8 != 0 { names.push("B"); }
if value & RawFlag::C as u8 != 0 { names.push("C"); }
names
}
// Bitflags approach (full runtime inspection)
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct TypedFlags: u8 {
const A = 0b001;
const B = 0b010;
const C = 0b100;
}
}
fn main() {
// Raw: manual bit manipulation
let raw_value: u8 = 0b011;
println!("Raw has A: {}", has_flag_raw(raw_value, RawFlag::A));
println!("Raw flags: {:?}", list_flags_raw(raw_value));
// Bitflags: semantic operations
let flags = TypedFlags::A | TypedFlags::B;
println!("Typed has A: {}", flags.contains(TypedFlags::A));
println!("Typed flags: {:?}", flags.iter().collect::<Vec<_>>());
// Bitflags provides type safety
// let bad = TypedFlags::A | 0b1000; // Compile error
// But raw: let bad = RawFlag::A as u8 | 0b1000; // Works, no type safety
}Bitflags provides type safety and runtime inspection that raw enums lack.
use bitflags::bitflags;
use std::time::Instant;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct FastFlags: u64 {
const A = 1 << 0;
const B = 1 << 1;
const C = 1 << 2;
const D = 1 << 3;
const E = 1 << 4;
}
}
fn main() {
let iterations = 10_000_000;
// Raw bitwise check (fastest)
let start = Instant::now();
let mut count = 0u64;
for i in 0..iterations {
let value = (i % 32) as u64;
if value & 0b0001 != 0 {
count += 1;
}
}
let raw_time = start.elapsed();
// Contains check (slightly slower due to trait dispatch)
let start = Instant::now();
let mut count = 0u64;
for i in 0..iterations {
let flags = FastFlags::from_bits_truncate((i % 32) as u64);
if flags.contains(FastFlags::A) {
count += 1;
}
}
let flags_time = start.elapsed();
// Iter (more overhead)
let start = Instant::now();
let mut count = 0u64;
for i in 0..iterations {
let flags = FastFlags::from_bits_truncate((i % 32) as u64);
count += flags.iter().count() as u64;
}
let iter_time = start.elapsed();
println!("Raw bitwise: {:?}", raw_time);
println!("Contains check: {:?}", flags_time);
println!("Iterate flags: {:?}", iter_time);
// Overhead is minimal for contains(), significant for iter()
// Choose based on whether you need semantic operations
}contains() has minimal overhead; iter() has more but provides semantic information.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct Config: u8 {
const ENABLED = 1;
const VISIBLE = 2;
const VERBOSE = 4;
}
}
fn main() {
// External input might have unknown bits
let external_value: u8 = 0b11111; // Has extra bits
// from_bits: strict validation
match Config::from_bits(external_value) {
Some(config) => println!("Valid config: {:?}", config),
None => println!("Invalid config: contains unknown bits"),
}
// from_bits_truncate: strip unknown bits
let truncated = Config::from_bits_truncate(external_value);
println!("Truncated config: {:?}", truncated);
println!("Known bits only: {:b}", truncated.bits());
// from_bits_unchecked: unsafe, assumes valid (avoid in production)
// let unchecked = unsafe { Config::from_bits_unchecked(external_value) };
// Check if value equals known flags exactly
let is_valid = Config::from_bits(external_value).is_some();
println!("Is known flags exactly: {}", is_valid);
// all() gives mask of all known flags
let all_known = Config::all().bits();
println!("All known bits: {:b}", all_known);
}Choose validation strategy based on data source trust level.
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct FeatureFlags: u32 {
const DARK_MODE = 1 << 0;
const NOTIFICATIONS = 1 << 1;
const AUTO_SAVE = 1 << 2;
const SYNC = 1 << 3;
const ANALYTICS = 1 << 4;
const DEBUG_MODE = 1 << 5;
}
}
struct AppConfig {
features: FeatureFlags,
}
impl AppConfig {
fn new(features: FeatureFlags) -> Self {
AppConfig { features }
}
fn from_raw(raw: u32) -> Result<Self, String> {
FeatureFlags::from_bits(raw)
.ok_or_else(|| format!("Unknown feature bits: {:b}", raw))
.map(|features| AppConfig { features })
}
fn is_enabled(&self, feature: FeatureFlags) -> bool {
self.features.contains(feature)
}
fn enable(&mut self, feature: FeatureFlags) {
self.features |= feature;
}
fn disable(&mut self, feature: FeatureFlags) {
self.features -= feature;
}
fn list_enabled(&self) -> Vec<&'static str> {
self.features.iter_names().map(|(name, _)| name).collect()
}
fn diff(&self, other: &AppConfig) -> FeatureFlags {
self.features ^ other.features
}
}
fn main() {
let mut config = AppConfig::new(FeatureFlags::DARK_MODE | FeatureFlags::AUTO_SAVE);
println!("Enabled features: {:?}", config.list_enabled());
config.enable(FeatureFlags::NOTIFICATIONS);
config.disable(FeatureFlags::AUTO_SAVE);
println!("After changes: {:?}", config.list_enabled());
// Toggle
config.features.toggle(FeatureFlags::DEBUG_MODE);
println!("With debug: {:?}", config.features);
// Intersection
let required = FeatureFlags::DARK_MODE | FeatureFlags::SYNC;
println!("Has required: {}", config.features.contains(required));
// Difference between configs
let other = AppConfig::new(FeatureFlags::DARK_MODE | FeatureFlags::DEBUG_MODE);
let diff = config.diff(&other);
println!("Different flags: {:?}", diff);
}The Flags trait enables rich configuration management with runtime inspection.
Core distinction:
Flags trait: semantic operations with runtime introspectionWhat Flags provides:
.iter(): enumerate all set flags as typed values.iter_names(): enumerate flag names and values.from_bits(): validate and convert raw bits.from_bits_truncate(): convert, stripping unknown bits.from_name(): convert string name to flag value.contains(): semantic check for flag presence.bits(): access raw underlying valueRuntime inspection capabilities:
Performance:
contains() has minimal overhead (near bitwise &)iter() has more overhead (loop over all flags)When to use each:
Flags trait: Configuration, serialization, debugging, CLI tools, user-facing codeKey insight: The Flags trait transforms opaque bit patterns into self-documenting values. With raw bitwise operations, 0b0110 is just a numberâyou must remember it means "WRITE | EXECUTE". With Flags, you can iterate, display, and validate these values at runtime. This doesn't replace bitwise operationsâit provides semantic layers on top. Use Flags for any code that needs to communicate flag states (logging, config files, CLI), and fall back to raw .bits() for performance-critical sections. The type safety prevents combining flags from different flag types and ensures you're always working with valid combinations.