Loading page…
Rust walkthroughs
Loading page…
bitflags::Flags trait and how does it enable generic code over flag types?The bitflags::Flags trait provides a standardized interface for types that represent bitwise flags, enabling generic code that works with any flags type regardless of its specific implementation. The trait defines methods for checking flag presence (contains), inserting flags (insert), removing flags (remove), iterating over set flags (iter), and accessing flag metadata. By abstracting over specific flag types, Flags allows you to write functions and structs that operate on flags generically, supporting operations like validation, serialization, intersection, and union without knowing the concrete flag type at compile time.
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 0b001;
const WRITE = 0b010;
const EXECUTE = 0b100;
}
}
fn main() {
let perms = Permissions::READ | Permissions::WRITE;
println!("{:?}", perms);
// Permissions(READ | WRITE)
}The bitflags! macro generates a struct implementing the Flags trait.
use bitflags::Flags;
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Flags: u8 {
const A = 0b001;
const B = 0b010;
const C = 0b100;
}
}
fn main() {
let flags = Flags::A | Flags::B;
// Check if flags contains all specified flags
println!("Contains A: {}", flags.contains(Flags::A)); // true
println!("Contains C: {}", flags.contains(Flags::C)); // false
// Check if flags contains any of the specified flags
println!("Intersects B: {}", flags.intersects(Flags::B)); // true
println!("Intersects C: {}", flags.intersects(Flags::C)); // false
// Check if no flags are set
println!("Is empty: {}", flags.is_empty()); // false
println!("Is empty: {}", Flags::empty().is_empty()); // true
// Get all possible flags
println!("All: {:?}", Flags::all()); // A | B | C
println!("Empty: {:?}", Flags::empty()); // (empty)
}The Flags trait provides methods for querying flag state.
use bitflags::Flags;
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Options: u8 {
const VERBOSE = 1;
const QUIET = 2;
const DEBUG = 4;
}
}
// Generic function that works with any Flags type
fn print_flags<F: Flags>(name: &str, flags: F) {
println!("{}: {:?} (bits: {:b})", name, flags, flags.bits());
}
fn main() {
let perms = Permissions::READ | Permissions::WRITE;
let opts = Options::VERBOSE | Options::DEBUG;
print_flags("Permissions", perms);
print_flags("Options", opts);
}Generic functions can operate on any type implementing Flags.
use bitflags::Flags;
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
fn main() {
let perms = Permissions::READ | Permissions::WRITE | Permissions::EXECUTE;
// Iterate over all set flags
for flag in perms.iter() {
println!("Flag: {:?}", flag);
}
// Flag: READ
// Flag: WRITE
// Flag: EXECUTE
}The iter() method yields each set flag individually.
use bitflags::{Flags, bits};
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Status: u8 {
const ACTIVE = 1;
const PENDING = 2;
const SUSPENDED = 4;
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Mode: u8 {
const READ_ONLY = 1;
const READ_WRITE = 2;
const ADMIN = 4;
}
}
// Validate that flags are valid combinations
fn validate_flags<F: Flags>(flags: F) -> bool {
// Check that all bits correspond to defined flags
flags.bits() & !F::all().bits() == F::empty().bits()
}
fn main() {
let valid_status = Status::ACTIVE | Status::PENDING;
let valid_mode = Mode::READ_ONLY;
println!("Status valid: {}", validate_flags(valid_status)); // true
println!("Mode valid: {}", validate_flags(valid_mode)); // true
// Create invalid flags (bit pattern with undefined flags)
let invalid_status = Status::from_bits_truncate(0b11111111);
println!("Invalid status: {:?}", invalid_status); // All defined flags set
}The Flags trait enables validation of flag values generically.
use bitflags::Flags;
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
fn main() {
let mut flags = Permissions::READ;
// Insert flags
flags.insert(Permissions::WRITE);
println!("After insert: {:?}", flags); // READ | WRITE
// Remove flags
flags.remove(Permissions::READ);
println!("After remove: {:?}", flags); // WRITE
// Toggle flags
flags.toggle(Permissions::WRITE);
println!("After toggle: {:?}", flags); // (empty)
flags.toggle(Permissions::WRITE);
println!("After toggle: {:?}", flags); // WRITE
// Set to exact value
flags.set(Permissions::READ, true);
flags.set(Permissions::EXECUTE, true);
println!("After set: {:?}", flags); // WRITE | READ | EXECUTE
}The Flags trait provides methods for modifying flags.
use bitflags::Flags;
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
fn main() {
let flags1 = Permissions::READ | Permissions::WRITE;
let flags2 = Permissions::WRITE | Permissions::EXECUTE;
// Union (OR)
let union = flags1 | flags2;
println!("Union: {:?}", union); // READ | WRITE | EXECUTE
// Intersection (AND)
let intersection = flags1 & flags2;
println!("Intersection: {:?}", intersection); // WRITE
// Difference
let difference = flags1 - flags2;
println!("Difference: {:?}", difference); // READ
// Symmetric difference (XOR)
let sym_diff = flags1 ^ flags2;
println!("Symmetric diff: {:?}", sym_diff); // READ | EXECUTE
}Bitwise operators work on any Flags type.
use bitflags::Flags;
use bitflags::bitflags;
use std::collections::HashMap;
use std::hash::Hash;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
struct Permissions: u32 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
struct Access: u8 {
const PUBLIC = 1;
const PRIVATE = 2;
const PROTECTED = 4;
}
}
// Store flags in a collection
struct FlagRegistry<F: Flags> {
entries: HashMap<String, F>,
}
impl<F: Flags + Hash + Eq + Copy> FlagRegistry<F> {
fn new() -> Self {
FlagRegistry {
entries: HashMap::new(),
}
}
fn register(&mut self, name: &str, flags: F) {
self.entries.insert(name.to_string(), flags);
}
fn get(&self, name: &str) -> Option<F> {
self.entries.get(name).copied()
}
fn find_with_flag(&self, flag: F) -> Vec<&str> {
self.entries
.iter()
.filter(|(_, &flags)| flags.contains(flag))
.map(|(name, _)| name.as_str())
.collect()
}
}
fn main() {
let mut perm_registry = FlagRegistry::new();
perm_registry.register("file1", Permissions::READ | Permissions::WRITE);
perm_registry.register("file2", Permissions::READ);
let mut access_registry = FlagRegistry::new();
access_registry.register("doc1", Access::PUBLIC);
access_registry.register("doc2", Access::PRIVATE);
// Works with both flag types
if let Some(perms) = perm_registry.get("file1") {
println!("file1 permissions: {:?}", perms);
}
let read_files = perm_registry.find_with_flag(Permissions::READ);
println!("Files with READ: {:?}", read_files);
}The Flags trait enables storing any flag type in generic collections.
use bitflags::Flags;
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
fn main() {
// From bits - returns Option, validates bits
let valid = Permissions::from_bits(3); // READ | WRITE
println!("Valid: {:?}", valid); // Some(READ | WRITE)
let invalid = Permissions::from_bits(8); // No defined flag has this value
println!("Invalid: {:?}", invalid); // None
// From bits truncate - keeps only valid bits
let truncated = Permissions::from_bits_truncate(0b11111);
println!("Truncated: {:?}", truncated); // READ | WRITE | EXECUTE
// From bits retain - keeps all bits (unsafe for unknown bits)
let retained = Permissions::from_bits_retain(0b11111);
println!("Retained: {:?}", retained); // Includes undefined bits
}The Flags trait provides multiple ways to convert from raw bits.
use bitflags::Flags;
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
fn main() {
let perms = Permissions::READ | Permissions::WRITE;
// Get the underlying bits
let bits = perms.bits();
println!("Bits: {:b}", bits); // 11
// Access the empty value
let empty = Permissions::empty();
println!("Empty bits: {:b}", empty.bits()); // 0
// Access all flags
let all = Permissions::all();
println!("All bits: {:b}", all.bits()); // 111
}The bits() method returns the underlying storage type.
use bitflags::{Flags, Bits};
use bitflags::bitflags;
// Bits type varies based on storage
bitflags! {
struct Flags8: u8 { const A = 1; }
struct Flags16: u16 { const A = 1; }
struct Flags32: u32 { const A = 1; }
struct Flags64: u64 { const A = 1; }
struct Flags128: u128 { const A = 1; }
}
// Generic function using Bits trait
fn bits_value<F: Flags>() -> <F as Flags>::Bits
where
<F as Flags>::Bits: Default,
{
<F as Flags>::Bits::default()
}
fn main() {
let f8 = Flags8::A;
let f16 = Flags16::A;
println!("8-bit: {}", f8.bits());
println!("16-bit: {}", f16.bits());
}Each flags type has an associated Bits type for the underlying storage.
use bitflags::Flags;
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
fn main() {
let perms = Permissions::READ | Permissions::WRITE;
// Iterate over individual flags
println!("Set flags:");
for flag in perms.iter() {
println!(" {:?}", flag);
}
// Iterate over all defined flags
println!("All defined flags:");
for flag in Permissions::all().iter() {
println!(" {:?}", flag);
}
}The iter() method yields each flag separately.
use bitflags::Flags;
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
// Generic function to combine flags
fn combine_flags<F: Flags + Copy>(flags: &[F]) -> F {
flags.iter().fold(F::empty(), |acc, &f| acc | f)
}
// Generic function to check any flag
fn has_any_flag<F: Flags>(flags: F, check: F) -> bool {
flags.intersects(check)
}
// Generic function to check all flags
fn has_all_flags<F: Flags>(flags: F, check: F) -> bool {
flags.contains(check)
}
fn main() {
let combined = combine_flags(&[
Permissions::READ,
Permissions::WRITE,
]);
println!("Combined: {:?}", combined);
println!("Has READ or WRITE: {}", has_any_flag(combined, Permissions::READ));
println!("Has READ and WRITE: {}", has_all_flags(combined, Permissions::READ | Permissions::WRITE));
}The Flags trait enables generic flag manipulation functions.
use bitflags::Flags;
use bitflags::bitflags;
use serde::{Serialize, Deserialize};
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Permissions: u32 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
fn main() {
let perms = Permissions::READ | Permissions::WRITE;
// Serialize
let json = serde_json::to_string(&perms).unwrap();
println!("JSON: {}", json);
// Deserialize
let deserialized: Permissions = serde_json::from_str(&json).unwrap();
println!("Deserialized: {:?}", deserialized);
}Flag types can implement serialization traits.
use bitflags::Flags;
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
struct Config: u32 {
const ENABLED = 1;
const VERBOSE = 2;
const DEBUG = 4;
const SECURE = 8;
}
}
impl Config {
fn is_secure(&self) -> bool {
self.contains(Config::SECURE)
}
fn enable_secure(&mut self) {
self.insert(Config::SECURE);
}
}
fn main() {
let mut config = Config::default();
config.insert(Config::ENABLED | Config::VERBOSE);
if config.is_secure() {
println!("Running in secure mode");
}
config.enable_secure();
println!("Config: {:?}", config);
}Custom methods can be added to flag types.
use bitflags::{Flags, Bits};
use bitflags::bitflags;
use std::ops::{BitOr, BitAnd, BitOrAssign};
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Status: u8 {
const ACTIVE = 1;
const PENDING = 2;
const ERROR = 4;
}
}
// Function requiring full Flags capabilities
fn merge_flags<F>(flags1: F, flags2: F) -> F
where
F: Flags + Copy + BitOr<Output = F>,
{
flags1 | flags2
}
// Function requiring Bits access
fn get_bits<F>(flags: F) -> <F as Flags>::Bits
where
F: Flags,
<F as Flags>::Bits: Copy,
{
flags.bits()
}
// Function requiring modification
fn set_flag<F>(flags: &mut F, flag: F)
where
F: Flags,
{
flags.insert(flag);
}
fn main() {
let merged = merge_flags(Status::ACTIVE, Status::PENDING);
println!("Merged: {:?}", merged);
let bits = get_bits(Status::ACTIVE);
println!("Bits: {}", bits);
let mut status = Status::PENDING;
set_flag(&mut status, Status::ERROR);
println!("Modified: {:?}", status);
}Use appropriate trait bounds for generic flag operations.
use bitflags::Flags;
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
fn main() {
let perm1 = Permissions::READ | Permissions::WRITE;
let perm2 = Permissions::READ | Permissions::WRITE;
let perm3 = Permissions::READ | Permissions::EXECUTE;
// Equality comparison
println!("perm1 == perm2: {}", perm1 == perm2); // true
println!("perm1 == perm3: {}", perm1 == perm3); // false
// Subset check
println!("perm1 contains READ: {}", perm1.contains(Permissions::READ)); // true
println!("perm1 subset of all: {}", perm1.contains(Permissions::all())); // false
// Superset check
let all = Permissions::all();
println!("all superset of perm1: {}", all.contains(perm1)); // true
}Flags implement PartialEq and Eq for comparison.
Flags trait purpose:
Key methods:
contains(): Check if all specified flags are setintersects(): Check if any specified flags are setinsert(): Set specified flagsremove(): Clear specified flagstoggle(): Flip specified flagsiter(): Iterate over set flagsbits(): Access underlying storagefrom_bits(): Convert from bits with validationfrom_bits_truncate(): Convert, discarding unknown bitsWhen to use generic Flags:
Best practices:
Copy, Clone, Debug, PartialEq, Eq for all flagsHash if using as hash map keysfrom_bits_truncate for untrusted inputfrom_bits for validation of unknown bitsKey insight: The Flags trait transforms ad-hoc bit manipulation into a well-typed, composable abstraction. Instead of working with raw integers and bit operations, you work with named flags that the type system understands. The trait enables writing code that operates on flags generically, supporting any combination of flags while maintaining type safety and readability.