Loading page…
Rust walkthroughs
Loading page…
bitflags crate's bitflags! macro and how does it improve type safety over raw integer flags?Bit flags are a common pattern for representing sets of options or states, but using raw integers for flags invites bugs. The bitflags! macro creates dedicated types for bit flag sets, providing type safety, readable code, and compile-time guarantees that raw integer flags cannot offer.
Before bitflags, flags are typically defined as integer constants:
const READ: u32 = 0b0001;
const WRITE: u32 = 0b0010;
const EXECUTE: u32 = 0b0100;
fn check_permissions(permissions: u32) -> bool {
(permissions & READ) != 0
}
fn main() {
// Combine flags with bitwise OR
let perms = READ | WRITE;
// Check flags with bitwise AND
if perms & READ != 0 {
println!("Can read");
}
// Problem: Any u32 is valid
check_permissions(42); // What flags is this?
check_permissions(0xFFFF); // Invalid flags?
}Raw integers accept any value, including invalid combinations or values outside the defined flags.
Raw integer flags have several problems:
const FILE_READ: u32 = 0b0001;
const FILE_WRITE: u32 = 0b0010;
const SOCKET_READ: u32 = 0b0001;
const SOCKET_WRITE: u32 = 0b0010;
fn process_file(flags: u32) {
// Expects FILE_* flags
}
fn process_socket(flags: u32) {
// Expects SOCKET_* flags
}
fn main() {
// No type distinction between file flags and socket flags
let file_flags = FILE_READ | FILE_WRITE;
// This compiles but is semantically wrong!
process_socket(file_flags);
// Invalid flag values accepted
process_file(999);
// Typos in hex/binary literals
process_file(0b001); // Meant 0b0001?
}Any u32 can be passed where flags are expected, with no compile-time verification.
The bitflags! macro creates a dedicated type:
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 0b0001;
const WRITE = 0b0010;
const EXECUTE = 0b0100;
}
}
fn main() {
// Create a flags value
let perms = Permissions::READ | Permissions::WRITE;
// Type-checked: only Permissions values are valid
check_permissions(perms);
// This would not compile:
// check_permissions(42);
// check_permissions(0b0111);
}
fn check_permissions(perms: Permissions) {
if perms.contains(Permissions::READ) {
println!("Can read");
}
}The Permissions type is distinct from u32, preventing misuse.
Different flag sets are distinct types:
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct FileFlags: u32 {
const READ = 0b0001;
const WRITE = 0b0010;
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct SocketFlags: u32 {
const READ = 0b0001;
const WRITE = 0b0010;
const CONNECT = 0b0100;
}
}
fn process_file(flags: FileFlags) { /* ... */ }
fn process_socket(flags: SocketFlags) { /* ... */ }
fn main() {
let file_flags = FileFlags::READ | FileFlags::WRITE;
let socket_flags = SocketFlags::READ | SocketFlags::CONNECT;
process_file(file_flags);
process_socket(socket_flags);
// Type mismatch - won't compile
// process_file(socket_flags);
// process_socket(file_flags);
}Each flags type is a separate, incompatible type.
Instead of manual bitwise operations:
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 0b0001;
const WRITE = 0b0010;
const EXECUTE = 0b0100;
}
}
fn check_perms_raw(perms: u32) {
// Easy to get wrong
if perms & 0b0001 != 0 { // What flag is this?
println!("Can read");
}
}
fn check_perms_typed(perms: Permissions) {
// Clear and type-safe
if perms.contains(Permissions::READ) {
println!("Can read");
}
// Check multiple flags
if perms.contains(Permissions::READ | Permissions::WRITE) {
println!("Can read and write");
}
// Check for any of several flags
if perms.intersects(Permissions::READ | Permissions::WRITE) {
println!("Can read or write (or both)");
}
}The methods are self-documenting and type-checked.
The bitflags! type supports idiomatic combinations:
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 0b0001;
const WRITE = 0b0010;
const EXECUTE = 0b0100;
}
}
fn main() {
let mut perms = Permissions::empty();
// Add flags
perms |= Permissions::READ;
perms |= Permissions::WRITE;
// Remove flags
perms &= !Permissions::WRITE;
// Toggle flags
perms ^= Permissions::EXECUTE;
// Set to exact value
perms = Permissions::READ | Permissions::WRITE;
// Check if empty or full
if perms.is_empty() {
println!("No permissions");
}
// Check equality
if perms == Permissions::READ {
println!("Read only");
}
}The bit operations work naturally while maintaining type safety.
Common flag patterns are handled elegantly:
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Flags: u32 {
const A = 0b0001;
const B = 0b0010;
const C = 0b0100;
}
}
fn main() {
// No flags set
let empty = Flags::empty();
assert!(empty.is_empty());
// All defined flags set
let all = Flags::all();
assert!(all.contains(Flags::A | Flags::B | Flags::C));
// Common patterns
let default = Flags::empty(); // No permissions
let full_access = Flags::all(); // All permissions
// Check if all flags are set
let some = Flags::A | Flags::B;
assert!(!some.is_all());
assert!(all.is_all());
}Interoperability with external APIs:
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 0b0001;
const WRITE = 0b0010;
const EXECUTE = 0b0100;
}
}
fn main() {
// From bits - includes unknown bits
let from_raw = Permissions::from_bits(0b0111);
assert!(from_raw.is_some());
// From bits - fails if unknown bits present
let strict = Permissions::from_bits_truncate(0b1111);
// Truncates unknown bit (0b1000)
assert_eq!(strict, Permissions::READ | Permissions::WRITE | Permissions::EXECUTE);
// From bits - returns None if any unknown bits
let strict_option = Permissions::from_bits(0b1111);
assert!(strict_option.is_none()); // Unknown bit 0b1000
// To bits
let perms = Permissions::READ | Permissions::WRITE;
let raw: u32 = perms.bits();
assert_eq!(raw, 0b0011);
// Interop with external C API
// let result = unsafe { c_function(perms.bits()) };
}The from_bits and from_bits_truncate methods handle external integer values safely.
Flag values format with human-readable names:
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 0b0001;
const WRITE = 0b0010;
const EXECUTE = 0b0100;
}
}
fn main() {
let perms = Permissions::READ | Permissions::WRITE;
// Debug output shows flag names
println!("{:?}", perms); // Permissions::READ | Permissions::WRITE
// Empty flags
let empty = Permissions::empty();
println!("{:?}", empty); // Permissions(empty)
// Unknown bits are shown as hex
let with_unknown = Permissions::from_bits_truncate(0b1000);
println!("{:?}", with_unknown); // Permissions(0x8)
}This is invaluable for debugging compared to raw integer values.
use bitflags::bitflags;
// Raw integer approach
const RAW_READ: u32 = 1;
const RAW_WRITE: u32 = 2;
fn debug_raw(perms: u32) {
println!("Permissions: {}", perms); // "Permissions: 3" - meaningless
}
// bitflags approach
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 1;
const WRITE = 2;
}
}
fn debug_typed(perms: Permissions) {
println!("Permissions: {:?}", perms); // "Permissions: READ | WRITE"
}External systems may have flags you don't know about:
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct KnownFlags: u32 {
const A = 0b0001;
const B = 0b0010;
// Maybe the system has more flags we don't know about
}
}
fn main() {
// External value with unknown bit
let external = 0b0111;
// from_bits fails if any unknown bits
let strict = KnownFlags::from_bits(external);
assert!(strict.is_none());
// from_bits_truncate removes unknown bits
let truncated = KnownFlags::from_bits_truncate(external);
assert_eq!(truncated, KnownFlags::A | KnownFlags::B);
// from_bits_unchecked is unsafe - preserves unknown bits
// unsafe {
// let unchecked = KnownFlags::from_bits_unchecked(external);
// }
// from_bits_retain keeps unknown bits (bitflags 2.x)
let retained = KnownFlags::from_bits_retain(external);
assert_eq!(retained.bits(), 0b0111);
}use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 0b0001;
const WRITE = 0b0010;
const EXECUTE = 0b0100;
}
}
fn main() {
let perms = Permissions::READ | Permissions::EXECUTE;
// Iterate over set flags
for flag in perms.iter() {
match flag {
Permissions::READ => println!("Read permission"),
Permissions::WRITE => println!("Write permission"),
Permissions::EXECUTE => println!("Execute permission"),
_ => println!("Unknown flag"),
}
}
}use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Flags: u32 {
const A = 0b0001;
const B = 0b0010;
const C = 0b0100;
}
}
fn main() {
let set1 = Flags::A | Flags::B;
let set2 = Flags::B | Flags::C;
// Union (flags in either set)
let union = set1 | set2;
assert_eq!(union, Flags::A | Flags::B | Flags::C);
// Intersection (flags in both sets)
let intersection = set1 & set2;
assert_eq!(intersection, Flags::B);
// Difference (flags in first but not second)
let difference = set1 - set2;
assert_eq!(difference, Flags::A);
// Symmetric difference (flags in exactly one set)
let sym_diff = set1 ^ set2;
assert_eq!(sym_diff, Flags::A | Flags::C);
// Toggle specific flags
let toggled = set1 ^ Flags::A;
assert_eq!(toggled, Flags::B);
}use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct Config: u32 {
// Default is empty (no flags set)
const DEBUG = 0b0001;
const VERBOSE = 0b0010;
const LOG_FILE = 0b0100;
}
}
fn main() {
// Default is empty flags
let config = Config::default();
assert!(config.is_empty());
// Create with default and modify
let mut config = Config::default();
if std::env::var("DEBUG").is_ok() {
config |= Config::DEBUG;
}
}use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Permissions: u32 {
const READ = 0b0001;
const WRITE = 0b0010;
const EXECUTE = 0b0100;
}
}
// With serde feature enabled:
// bitflags! {
// #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
// struct Permissions: u32 {
// const READ = 0b0001;
// const WRITE = 0b0010;
// }
// }
// Can serialize as integer or as list of strings
fn serialize_example() {
let perms = Permissions::READ | Permissions::WRITE;
// As integer: {"permissions": 3}
// As strings: {"permissions": ["READ", "WRITE"]}
}use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct FileMode: u32 {
const USER_READ = 0o400;
const USER_WRITE = 0o200;
const USER_EXECUTE = 0o100;
const GROUP_READ = 0o040;
const GROUP_WRITE = 0o020;
const GROUP_EXECUTE = 0o010;
const OTHER_READ = 0o004;
const OTHER_WRITE = 0o002;
const OTHER_EXECUTE = 0o001;
}
}
fn main() {
// Common modes
let read_write = FileMode::USER_READ | FileMode::USER_WRITE
| FileMode::GROUP_READ;
let executable = FileMode::USER_READ | FileMode::USER_EXECUTE;
// Check permissions
if read_write.contains(FileMode::USER_WRITE) {
println!("User can write");
}
// Combine modes
let full = read_write | FileMode::USER_EXECUTE;
// Convert to Unix mode bits
println!("Mode: {:o}", full.bits()); // Mode: 700
}The bitflags! macro creates dedicated types for bit flag sets that solve the problems of raw integer flags:
Type Safety: Each flag set is a distinct type. You cannot accidentally mix FileFlags with SocketFlags or pass arbitrary integers where flags are expected.
Self-Documenting Code: flags.contains(Flags::READ) is clearer than flags & 0b0001 != 0. Debug output shows flag names instead of cryptic numbers.
Compile-Time Guarantees: The compiler ensures only valid flag values are used. Typos in flag names are caught at compile time, not runtime.
Ergonomic API: Methods like contains, intersects, empty, all, and iter provide intuitive operations instead of error-prone bitwise math.
Safe Interoperability: from_bits, from_bits_truncate, and bits() allow safe conversion to and from raw integers for external API compatibility.
Zero-Cost Abstraction: The generated types compile down to the underlying integer type with no runtime overhead compared to raw flags.
The macro generates a struct with all the necessary trait implementations (BitOr, BitAnd, BitXor, Not, Debug, etc.) automatically, eliminating boilerplate while ensuring correctness. For any codebase using flags extensively, bitflags! transforms a common source of bugs into a compile-time-checked, readable, and maintainable pattern.