How do I work with Bitflags for Bitwise Flag Operations in Rust?
Walkthrough
Bitflags provides a macro for defining bitwise flag types. It generates types that can be combined with |, &, ^ operators and provide methods for checking, setting, and clearing flags.
Key concepts:
- Flags — Individual bit values
- BitFlags — Container for combined flags
- Operations —
|(union),&(intersection),^(xor),!(complement) - Methods —
contains,insert,remove,toggle
When to use Bitflags:
- Permission systems (read, write, execute)
- Configuration options
- State machine flags
- Feature toggles
- Protocol flags
When NOT to use Bitflags:
- Complex state (use enums or structs)
- More than 64 flags (use HashSet or BitSet)
- When readability is more important than efficiency
Code Examples
Basic Bitflags Definition
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 perms = Permissions::READ | Permissions::WRITE;
println!("{:?}", perms); // READ | WRITE
println!("Value: {}", perms.bits()); // 3
}Checking Flags
use bitflags::bitflags;
bitflags! {
#[derive(Debug)]
struct Flags: u8 {
const A = 0b0000_0001;
const B = 0b0000_0010;
const C = 0b0000_0100;
}
}
fn main() {
let flags = Flags::A | Flags::C;
// Check if specific flag is set
println!("Has A: {}", flags.contains(Flags::A)); // true
println!("Has B: {}", flags.contains(Flags::B)); // false
println!("Has C: {}", flags.contains(Flags::C)); // true
// Check if all specified flags are set
println!("Has A and C: {}", flags.contains(Flags::A | Flags::C)); // true
println!("Has A and B: {}", flags.contains(Flags::A | Flags::B)); // false
// Check if any flag is set
println!("Has any: {}", !flags.is_empty());
}Modifying Flags
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct Config: u16 {
const DEBUG = 1 << 0;
const VERBOSE = 1 << 1;
const LOG_FILE = 1 << 2;
const LOG_STDERR = 1 << 3;
}
}
fn main() {
let mut config = Config::DEBUG;
// Insert flags
config.insert(Config::VERBOSE);
println!("After insert: {:?}", config);
// Remove flags
config.remove(Config::DEBUG);
println!("After remove: {:?}", config);
// Toggle flags
config.toggle(Config::LOG_FILE);
println!("After toggle: {:?}", config);
// Set to exact value
config.set(Config::VERBOSE, false);
println!("After set false: {:?}", config);
}Bitwise Operations
use bitflags::bitflags;
bitflags! {
struct Colors: u8 {
const RED = 1;
const GREEN = 2;
const BLUE = 4;
}
}
fn main() {
let red = Colors::RED;
let green = Colors::GREEN;
// Union (OR)
let yellow = red | green; // RED | GREEN
println!("Yellow: {:?}", yellow);
// Intersection (AND)
let both = yellow & Colors::RED;
println!("Intersection: {:?}", both); // RED
// Difference
let diff = yellow & !Colors::RED;
println!("Difference: {:?}", diff); // GREEN
// XOR
let xor = yellow ^ Colors::RED;
println!("XOR: {:?}", xor); // GREEN
// Complement
let complement = !Colors::RED;
println!("Complement: {:?}", complement);
}File Permissions Example
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct FilePermission: u32 {
const OWNER_READ = 0o400;
const OWNER_WRITE = 0o200;
const OWNER_EXEC = 0o100;
const GROUP_READ = 0o040;
const GROUP_WRITE = 0o020;
const GROUP_EXEC = 0o010;
const OTHER_READ = 0o004;
const OTHER_WRITE = 0o002;
const OTHER_EXEC = 0o001;
// Combined constants
const OWNER_ALL = Self::OWNER_READ.bits() | Self::OWNER_WRITE.bits() | Self::OWNER_EXEC.bits();
const GROUP_ALL = Self::GROUP_READ.bits() | Self::GROUP_WRITE.bits() | Self::GROUP_EXEC.bits();
const OTHER_ALL = Self::OTHER_READ.bits() | Self::OTHER_WRITE.bits() | Self::OTHER_EXEC.bits();
}
}
fn main() {
// rwxr-xr-x
let mode = FilePermission::OWNER_ALL
| FilePermission::GROUP_READ
| FilePermission::GROUP_EXEC
| FilePermission::OTHER_READ
| FilePermission::OTHER_EXEC;
println!("Mode: {:o}", mode.bits()); // 755
println!("Owner can write: {}", mode.contains(FilePermission::OWNER_WRITE));
println!("Group can write: {}", mode.contains(FilePermission::GROUP_WRITE));
}With Serde Serialization
use bitflags::bitflags;
use serde::{Serialize, Deserialize};
bitflags! {
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
struct Features: u8 {
const FEATURE_A = 1;
const FEATURE_B = 2;
const FEATURE_C = 4;
}
}
fn main() {
let features = Features::FEATURE_A | Features::FEATURE_C;
// Serialize to JSON
let json = serde_json::to_string(&features).unwrap();
println!("JSON: {}", json);
// Deserialize from JSON
let parsed: Features = serde_json::from_str(&json).unwrap();
println!("Parsed: {:?}", parsed);
}Parsing from String
use bitflags::bitflags;
use std::str::FromStr;
bitflags! {
#[derive(Debug)]
struct Options: u32 {
const OPT_A = 1;
const OPT_B = 2;
const OPT_C = 4;
}
}
fn main() {
// Parse from hex string
let opts = Options::from_bits(0b101).unwrap();
println!("Parsed: {:?}", opts); // OPT_A | OPT_C
// From bits truncates unknown bits
let truncated = Options::from_bits_truncate(0b1111);
println!("Truncated: {:?}", truncated); // OPT_A | OPT_B | OPT_C
}Iterating Over Flags
use bitflags::bitflags;
bitflags! {
#[derive(Debug)]
struct Flags: u8 {
const A = 1;
const B = 2;
const C = 4;
const D = 8;
}
}
fn main() {
let flags = Flags::A | Flags::C | Flags::D;
// Iterate over set flags
for flag in flags.iter() {
println!("Flag: {:?}", flag);
}
// Count set flags
let count = flags.iter().count();
println!("Count: {}", count); // 3
}All and Empty
use bitflags::bitflags;
bitflags! {
#[derive(Debug)]
struct Flags: u8 {
const A = 1;
const B = 2;
const C = 4;
}
}
fn main() {
// All flags set
let all = Flags::all();
println!("All: {:?}", all); // A | B | C
// No flags set
let empty = Flags::empty();
println!("Empty: {:?}", empty);
// Check if empty
println!("Is empty: {}", empty.is_empty()); // true
println!("Is empty: {}", all.is_empty()); // false
}Checking Multiple Flags
use bitflags::bitflags;
bitflags! {
struct Capabilities: u16 {
const CAN_READ = 1;
const CAN_WRITE = 2;
const CAN_DELETE = 4;
const CAN_SHARE = 8;
}
}
fn main() {
let caps = Capabilities::CAN_READ | Capabilities::CAN_WRITE | Capabilities::CAN_SHARE;
// Check if all of these are set
let required = Capabilities::CAN_READ | Capabilities::CAN_WRITE;
if caps.contains(required) {
println!("Has read and write");
}
// Check if any of these are set
let any_of = Capabilities::CAN_DELETE | Capabilities::CAN_SHARE;
if caps.intersects(any_of) {
println!("Has delete or share");
}
// Check exact match
if caps == required | Capabilities::CAN_SHARE {
println!("Exact match!");
}
}Default Implementation
use bitflags::bitflags;
bitflags! {
#[derive(Debug)]
struct Settings: u8 {
const ENABLED = 1;
const VERBOSE = 2;
const DEBUG = 4;
}
}
impl Default for Settings {
fn default() -> Self {
Settings::ENABLED | Settings::VERBOSE
}
}
fn main() {
let settings = Settings::default();
println!("Default: {:?}", settings); // ENABLED | VERBOSE
}Combining with External Flags
use bitflags::bitflags;
bitflags! {
struct Status: u32 {
// Lower 8 bits for state
const STATE_MASK = 0xFF;
const STATE_INIT = 0;
const STATE_RUNNING = 1;
const STATE_STOPPED = 2;
// Upper bits for flags
const FLAG_ERROR = 1 << 8;
const FLAG_WARNING = 1 << 9;
const FLAG_MAINTENANCE = 1 << 10;
}
}
fn main() {
let status = Status::STATE_RUNNING | Status::FLAG_WARNING;
// Extract state
let state = status & Status::STATE_MASK;
println!("State: {:?}", state);
// Check flags
if status.contains(Status::FLAG_ERROR) {
println!("Error!");
}
if status.contains(Status::FLAG_WARNING) {
println!("Warning!");
}
}Type Safety
use bitflags::bitflags;
bitflags! {
struct FlagsA: u8 {
const X = 1;
}
}
bitflags! {
struct FlagsB: u8 {
const Y = 1;
}
}
fn main() {
let a = FlagsA::X;
let b = FlagsB::Y;
// This would be a type error:
// let combined = a | b; // Error: mismatched types
// Must use same type
let combined_a = a | FlagsA::from_bits_truncate(0); // OK
}Conditional Operations
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy)]
struct Mode: u8 {
const READ = 1;
const WRITE = 2;
const APPEND = 4;
const CREATE = 8;
const TRUNCATE = 16;
}
}
fn open_file(filename: &str, mode: Mode) {
println!("Opening {} with mode: {:?}", filename, mode);
if mode.contains(Mode::WRITE) && mode.contains(Mode::READ) {
println!("Read-write mode");
}
if mode.contains(Mode::CREATE) && !mode.contains(Mode::WRITE) {
println!("Warning: CREATE without WRITE");
}
}
fn main() {
open_file("test.txt", Mode::READ | Mode::WRITE | Mode::CREATE);
}Database Example
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ColumnFlags: u16 {
const PRIMARY_KEY = 1;
const NOT_NULL = 2;
const UNIQUE = 4;
const INDEXED = 8;
const AUTO_INCREMENT = 16;
const FOREIGN_KEY = 32;
}
}
struct Column {
name: String,
flags: ColumnFlags,
}
impl Column {
fn new(name: &str) -> Self {
Column {
name: name.to_string(),
flags: ColumnFlags::empty(),
}
}
fn primary(mut self) -> Self {
self.flags.insert(ColumnFlags::PRIMARY_KEY | ColumnFlags::NOT_NULL);
self
}
fn unique(mut self) -> Self {
self.flags.insert(ColumnFlags::UNIQUE | ColumnFlags::INDEXED);
self
}
fn nullable(mut self) -> Self {
self.flags.remove(ColumnFlags::NOT_NULL);
self
}
}
fn main() {
let id_column = Column::new("id").primary();
let name_column = Column::new("email").unique();
println!("ID flags: {:?}", id_column.flags);
println!("Email flags: {:?}", name_column.flags);
}Network Protocol Flags
use bitflags::bitflags;
bitflags! {
#[derive(Debug)]
struct TcpFlags: u8 {
const FIN = 1;
const SYN = 2;
const RST = 4;
const PSH = 8;
const ACK = 16;
const URG = 32;
}
}
fn analyze_packet(flags: TcpFlags) {
if flags.contains(TcpFlags::SYN | TcpFlags::ACK) {
println!("SYN-ACK packet");
} else if flags.contains(TcpFlags::SYN) {
println!("SYN packet (connection start)");
} else if flags.contains(TcpFlags::FIN) {
println!("FIN packet (connection close)");
} else if flags.contains(TcpFlags::RST) {
println!("RST packet (connection reset)");
}
}
fn main() {
analyze_packet(TcpFlags::SYN);
analyze_packet(TcpFlags::SYN | TcpFlags::ACK);
analyze_packet(TcpFlags::FIN | TcpFlags::ACK);
}Converting to/from Primitive
use bitflags::bitflags;
bitflags! {
#[derive(Debug)]
struct Flags: u8 {
const A = 1;
const B = 2;
const C = 4;
}
}
fn main() {
// Get raw bits
let flags = Flags::A | Flags::C;
let bits: u8 = flags.bits();
println!("Bits: {}", bits); // 5
// From bits (returns None for invalid)
let from_bits = Flags::from_bits(5);
println!("From bits: {:?}", from_bits); // Some(A | C)
// From bits (truncates invalid)
let truncated = Flags::from_bits_truncate(255);
println!("Truncated: {:?}", truncated); // A | B | C
// Check if bits are valid
let is_valid = Flags::is_valid_bit_pattern(255);
println!("Valid: {}", is_valid); // true for flags-defined bits only
}Summary
Bitflags Key Imports:
use bitflags::bitflags;Defining Bitflags:
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Flags: u8 {
const A = 1;
const B = 2;
const C = 4;
}
}Key Methods:
| Method | Description |
|---|---|
.contains(flag) |
Check if flag is set |
.insert(flag) |
Set flag(s) |
.remove(flag) |
Clear flag(s) |
.toggle(flag) |
Flip flag(s) |
.set(flag, bool) |
Set/clear based on bool |
.intersects(flag) |
Check if any flag matches |
.is_empty() |
No flags set |
.all() |
All flags set |
.iter() |
Iterate over set flags |
.bits() |
Get raw value |
Bitwise Operators:
| Operator | Name | Result |
|---|---|---|
| |
Union | Flags in either |
& |
Intersection | Flags in both |
^ |
XOR | Flags in one but not both |
! |
Complement | All flags not in operand |
Key Points:
- Use bitflags for efficient flag storage
- Each flag should be a single bit
- Combine flags with
|operator - Check with
contains()for all orintersects()for any .bits()returns raw integer.from_bits()validates,.from_bits_truncate()ignores unknown- Can derive Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize
- Great for permissions, options, states, protocols
