Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

bitflags

The bitflags crate provides a macro for generating type-safe bitmask structures. In compiler development, bitflags are essential for efficiently representing sets of boolean options or attributes that can be combined. Common use cases include representing file permissions, compiler optimization flags, access modifiers in programming languages, or node attributes in abstract syntax trees.

The primary advantage of bitflags over manual bit manipulation is type safety. Instead of working with raw integer constants and bitwise operations that can lead to errors, bitflags generates strongly-typed structures that prevent invalid flag combinations at compile time.

The type safety provided by bitflags becomes particularly valuable in large compiler codebases where flags may be passed through multiple layers of abstraction. The compiler ensures you cannot accidentally mix incompatible flag types or use undefined flag values.

Basic Usage

The bitflags! macro generates a struct that wraps an integer type and provides methods for safely manipulating sets of flags:

#![allow(unused)]
fn main() {
use bitflags::bitflags;

bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct FilePermissions: u32 {
        const READ = 0b0000_0001;
        const WRITE = 0b0000_0010;
        const EXECUTE = 0b0000_0100;
        const READ_WRITE = Self::READ.bits() | Self::WRITE.bits();
        const ALL = Self::READ.bits() | Self::WRITE.bits() | Self::EXECUTE.bits();
    }
}
}

This generates a struct with associated constants for each flag. The macro automatically implements common traits like Debug, Clone, and comparison operators. Each flag is assigned a bit position, and you can define composite flags that combine multiple bits.

Compiler Flags Example

A practical example in compiler development is managing compiler flags that control various aspects of the compilation process:

#![allow(unused)]
fn main() {
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct CompilerFlags: u32 {
        const OPTIMIZE = 1 << 0;
        const DEBUG_INFO = 1 << 1;
        const WARNINGS_AS_ERRORS = 1 << 2;
        const VERBOSE = 1 << 3;
        const LINK_TIME_OPTIMIZATION = 1 << 4;
        const STATIC_LINKING = 1 << 5;
        const PROFILE = 1 << 6;
        const RELEASE = Self::OPTIMIZE.bits() | Self::LINK_TIME_OPTIMIZATION.bits();
        const DEBUG = Self::DEBUG_INFO.bits() | Self::VERBOSE.bits();
    }
}
}

Note how we define composite flags like RELEASE and DEBUG that combine multiple individual flags. This pattern is common in compilers where certain modes imply specific sets of options.

Working with Flags

The generated types provide a rich API for manipulating flag sets. You can combine flags using the bitwise OR operator, check if specific flags are set, and perform set operations:

#![allow(unused)]
fn main() {
use bitflags::bitflags;
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct FilePermissions: u32 {
        const READ = 0b0000_0001;
        const WRITE = 0b0000_0010;
        const EXECUTE = 0b0000_0100;
        const READ_WRITE = Self::READ.bits() | Self::WRITE.bits();
        const ALL = Self::READ.bits() | Self::WRITE.bits() | Self::EXECUTE.bits();
    }
}
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct CompilerFlags: u32 {
        const OPTIMIZE = 1 << 0;
        const DEBUG_INFO = 1 << 1;
        const WARNINGS_AS_ERRORS = 1 << 2;
        const VERBOSE = 1 << 3;
        const LINK_TIME_OPTIMIZATION = 1 << 4;
        const STATIC_LINKING = 1 << 5;
        const PROFILE = 1 << 6;
        const RELEASE = Self::OPTIMIZE.bits() | Self::LINK_TIME_OPTIMIZATION.bits();
        const DEBUG = Self::DEBUG_INFO.bits() | Self::VERBOSE.bits();
    }
}
pub fn demonstrate_file_permissions() {
    let mut perms = FilePermissions::READ | FilePermissions::WRITE;
    println!("Initial permissions: {:?}", perms);
    println!("Can read: {}", perms.contains(FilePermissions::READ));
    println!("Can execute: {}", perms.contains(FilePermissions::EXECUTE));

    perms.insert(FilePermissions::EXECUTE);
    println!("After adding execute: {:?}", perms);

    perms.remove(FilePermissions::WRITE);
    println!("After removing write: {:?}", perms);

    let readonly = FilePermissions::READ;
    let readwrite = FilePermissions::READ_WRITE;
    println!(
        "Read-only intersects with read-write: {}",
        !readonly.intersection(readwrite).is_empty()
    );
}
#[derive(Debug)]
pub struct CompilerOptions {
    flags: CompilerFlags,
    optimization_level: u8,
}
impl CompilerOptions {
    pub fn new(flags: CompilerFlags) -> Self {
        let optimization_level = if flags.contains(CompilerFlags::OPTIMIZE) {
            if flags.contains(CompilerFlags::LINK_TIME_OPTIMIZATION) {
                3
            } else {
                2
            }
        } else {
            0
        };

        Self {
            flags,
            optimization_level,
        }
    }

    pub fn is_debug_build(&self) -> bool {
        self.flags.contains(CompilerFlags::DEBUG_INFO)
    }

    pub fn enable_profiling(&mut self) {
        self.flags.insert(CompilerFlags::PROFILE);
    }

    pub fn optimization_level(&self) -> u8 {
        self.optimization_level
    }
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_file_permissions() {
        let perms = FilePermissions::READ | FilePermissions::WRITE;
        assert!(perms.contains(FilePermissions::READ));
        assert!(perms.contains(FilePermissions::WRITE));
        assert!(!perms.contains(FilePermissions::EXECUTE));

        assert_eq!(
            FilePermissions::ALL,
            FilePermissions::READ | FilePermissions::WRITE | FilePermissions::EXECUTE
        );
    }

    #[test]
    fn test_compiler_flags() {
        let debug = CompilerFlags::DEBUG;
        assert!(debug.contains(CompilerFlags::DEBUG_INFO));
        assert!(debug.contains(CompilerFlags::VERBOSE));
        assert!(!debug.contains(CompilerFlags::OPTIMIZE));
    }

    #[test]
    fn test_compiler_options() {
        let release_options = CompilerOptions::new(CompilerFlags::RELEASE);
        assert!(!release_options.is_debug_build());
        assert_eq!(release_options.optimization_level, 3);

        let debug_options = CompilerOptions::new(CompilerFlags::DEBUG);
        assert!(debug_options.is_debug_build());
        assert_eq!(debug_options.optimization_level, 0);
    }
}
pub fn demonstrate_compiler_flags() {
    let debug_build = CompilerFlags::DEBUG;
    let release_build = CompilerFlags::RELEASE;

    println!("Debug flags: {:?}", debug_build);
    println!("Release flags: {:?}", release_build);

    let custom =
        CompilerFlags::OPTIMIZE | CompilerFlags::DEBUG_INFO | CompilerFlags::WARNINGS_AS_ERRORS;
    println!("Custom build flags: {:?}", custom);
    println!(
        "Custom has optimization: {}",
        custom.contains(CompilerFlags::OPTIMIZE)
    );

    let common = debug_build.intersection(release_build);
    println!("Common flags between debug and release: {:?}", common);
}
}

The contains method checks if specific flags are set, while intersection returns flags common to both sets. Other useful operations include union for combining flag sets, difference for flags in one set but not another, and toggle for flipping specific flags.

Integration with Compiler Structures

Bitflags integrate naturally with other compiler data structures. Here’s an example of using flags within a larger compiler options structure:

#![allow(unused)]
fn main() {
use bitflags::bitflags;
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct FilePermissions: u32 {
        const READ = 0b0000_0001;
        const WRITE = 0b0000_0010;
        const EXECUTE = 0b0000_0100;
        const READ_WRITE = Self::READ.bits() | Self::WRITE.bits();
        const ALL = Self::READ.bits() | Self::WRITE.bits() | Self::EXECUTE.bits();
    }
}
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct CompilerFlags: u32 {
        const OPTIMIZE = 1 << 0;
        const DEBUG_INFO = 1 << 1;
        const WARNINGS_AS_ERRORS = 1 << 2;
        const VERBOSE = 1 << 3;
        const LINK_TIME_OPTIMIZATION = 1 << 4;
        const STATIC_LINKING = 1 << 5;
        const PROFILE = 1 << 6;
        const RELEASE = Self::OPTIMIZE.bits() | Self::LINK_TIME_OPTIMIZATION.bits();
        const DEBUG = Self::DEBUG_INFO.bits() | Self::VERBOSE.bits();
    }
}
pub fn demonstrate_file_permissions() {
    let mut perms = FilePermissions::READ | FilePermissions::WRITE;
    println!("Initial permissions: {:?}", perms);
    println!("Can read: {}", perms.contains(FilePermissions::READ));
    println!("Can execute: {}", perms.contains(FilePermissions::EXECUTE));

    perms.insert(FilePermissions::EXECUTE);
    println!("After adding execute: {:?}", perms);

    perms.remove(FilePermissions::WRITE);
    println!("After removing write: {:?}", perms);

    let readonly = FilePermissions::READ;
    let readwrite = FilePermissions::READ_WRITE;
    println!(
        "Read-only intersects with read-write: {}",
        !readonly.intersection(readwrite).is_empty()
    );
}
pub fn demonstrate_compiler_flags() {
    let debug_build = CompilerFlags::DEBUG;
    let release_build = CompilerFlags::RELEASE;

    println!("Debug flags: {:?}", debug_build);
    println!("Release flags: {:?}", release_build);

    let custom =
        CompilerFlags::OPTIMIZE | CompilerFlags::DEBUG_INFO | CompilerFlags::WARNINGS_AS_ERRORS;
    println!("Custom build flags: {:?}", custom);
    println!(
        "Custom has optimization: {}",
        custom.contains(CompilerFlags::OPTIMIZE)
    );

    let common = debug_build.intersection(release_build);
    println!("Common flags between debug and release: {:?}", common);
}
impl CompilerOptions {
    pub fn new(flags: CompilerFlags) -> Self {
        let optimization_level = if flags.contains(CompilerFlags::OPTIMIZE) {
            if flags.contains(CompilerFlags::LINK_TIME_OPTIMIZATION) {
                3
            } else {
                2
            }
        } else {
            0
        };

        Self {
            flags,
            optimization_level,
        }
    }

    pub fn is_debug_build(&self) -> bool {
        self.flags.contains(CompilerFlags::DEBUG_INFO)
    }

    pub fn enable_profiling(&mut self) {
        self.flags.insert(CompilerFlags::PROFILE);
    }

    pub fn optimization_level(&self) -> u8 {
        self.optimization_level
    }
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_file_permissions() {
        let perms = FilePermissions::READ | FilePermissions::WRITE;
        assert!(perms.contains(FilePermissions::READ));
        assert!(perms.contains(FilePermissions::WRITE));
        assert!(!perms.contains(FilePermissions::EXECUTE));

        assert_eq!(
            FilePermissions::ALL,
            FilePermissions::READ | FilePermissions::WRITE | FilePermissions::EXECUTE
        );
    }

    #[test]
    fn test_compiler_flags() {
        let debug = CompilerFlags::DEBUG;
        assert!(debug.contains(CompilerFlags::DEBUG_INFO));
        assert!(debug.contains(CompilerFlags::VERBOSE));
        assert!(!debug.contains(CompilerFlags::OPTIMIZE));
    }

    #[test]
    fn test_compiler_options() {
        let release_options = CompilerOptions::new(CompilerFlags::RELEASE);
        assert!(!release_options.is_debug_build());
        assert_eq!(release_options.optimization_level, 3);

        let debug_options = CompilerOptions::new(CompilerFlags::DEBUG);
        assert!(debug_options.is_debug_build());
        assert_eq!(debug_options.optimization_level, 0);
    }
}
#[derive(Debug)]
pub struct CompilerOptions {
    flags: CompilerFlags,
    optimization_level: u8,
}
}
#![allow(unused)]
fn main() {
use bitflags::bitflags;
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct FilePermissions: u32 {
        const READ = 0b0000_0001;
        const WRITE = 0b0000_0010;
        const EXECUTE = 0b0000_0100;
        const READ_WRITE = Self::READ.bits() | Self::WRITE.bits();
        const ALL = Self::READ.bits() | Self::WRITE.bits() | Self::EXECUTE.bits();
    }
}
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct CompilerFlags: u32 {
        const OPTIMIZE = 1 << 0;
        const DEBUG_INFO = 1 << 1;
        const WARNINGS_AS_ERRORS = 1 << 2;
        const VERBOSE = 1 << 3;
        const LINK_TIME_OPTIMIZATION = 1 << 4;
        const STATIC_LINKING = 1 << 5;
        const PROFILE = 1 << 6;
        const RELEASE = Self::OPTIMIZE.bits() | Self::LINK_TIME_OPTIMIZATION.bits();
        const DEBUG = Self::DEBUG_INFO.bits() | Self::VERBOSE.bits();
    }
}
pub fn demonstrate_file_permissions() {
    let mut perms = FilePermissions::READ | FilePermissions::WRITE;
    println!("Initial permissions: {:?}", perms);
    println!("Can read: {}", perms.contains(FilePermissions::READ));
    println!("Can execute: {}", perms.contains(FilePermissions::EXECUTE));

    perms.insert(FilePermissions::EXECUTE);
    println!("After adding execute: {:?}", perms);

    perms.remove(FilePermissions::WRITE);
    println!("After removing write: {:?}", perms);

    let readonly = FilePermissions::READ;
    let readwrite = FilePermissions::READ_WRITE;
    println!(
        "Read-only intersects with read-write: {}",
        !readonly.intersection(readwrite).is_empty()
    );
}
pub fn demonstrate_compiler_flags() {
    let debug_build = CompilerFlags::DEBUG;
    let release_build = CompilerFlags::RELEASE;

    println!("Debug flags: {:?}", debug_build);
    println!("Release flags: {:?}", release_build);

    let custom =
        CompilerFlags::OPTIMIZE | CompilerFlags::DEBUG_INFO | CompilerFlags::WARNINGS_AS_ERRORS;
    println!("Custom build flags: {:?}", custom);
    println!(
        "Custom has optimization: {}",
        custom.contains(CompilerFlags::OPTIMIZE)
    );

    let common = debug_build.intersection(release_build);
    println!("Common flags between debug and release: {:?}", common);
}
#[derive(Debug)]
pub struct CompilerOptions {
    flags: CompilerFlags,
    optimization_level: u8,
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_file_permissions() {
        let perms = FilePermissions::READ | FilePermissions::WRITE;
        assert!(perms.contains(FilePermissions::READ));
        assert!(perms.contains(FilePermissions::WRITE));
        assert!(!perms.contains(FilePermissions::EXECUTE));

        assert_eq!(
            FilePermissions::ALL,
            FilePermissions::READ | FilePermissions::WRITE | FilePermissions::EXECUTE
        );
    }

    #[test]
    fn test_compiler_flags() {
        let debug = CompilerFlags::DEBUG;
        assert!(debug.contains(CompilerFlags::DEBUG_INFO));
        assert!(debug.contains(CompilerFlags::VERBOSE));
        assert!(!debug.contains(CompilerFlags::OPTIMIZE));
    }

    #[test]
    fn test_compiler_options() {
        let release_options = CompilerOptions::new(CompilerFlags::RELEASE);
        assert!(!release_options.is_debug_build());
        assert_eq!(release_options.optimization_level, 3);

        let debug_options = CompilerOptions::new(CompilerFlags::DEBUG);
        assert!(debug_options.is_debug_build());
        assert_eq!(debug_options.optimization_level, 0);
    }
}
impl CompilerOptions {
    pub fn new(flags: CompilerFlags) -> Self {
        let optimization_level = if flags.contains(CompilerFlags::OPTIMIZE) {
            if flags.contains(CompilerFlags::LINK_TIME_OPTIMIZATION) {
                3
            } else {
                2
            }
        } else {
            0
        };

        Self {
            flags,
            optimization_level,
        }
    }

    pub fn is_debug_build(&self) -> bool {
        self.flags.contains(CompilerFlags::DEBUG_INFO)
    }

    pub fn enable_profiling(&mut self) {
        self.flags.insert(CompilerFlags::PROFILE);
    }

    pub fn optimization_level(&self) -> u8 {
        self.optimization_level
    }
}
}

This pattern allows you to encapsulate flag-based configuration with derived state and behavior. The compiler options structure can make decisions based on flag combinations and expose higher-level methods that abstract over the underlying bit manipulation.

File Permissions Example

Another common use case is representing file permissions or access modifiers:

#![allow(unused)]
fn main() {
use bitflags::bitflags;
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct FilePermissions: u32 {
        const READ = 0b0000_0001;
        const WRITE = 0b0000_0010;
        const EXECUTE = 0b0000_0100;
        const READ_WRITE = Self::READ.bits() | Self::WRITE.bits();
        const ALL = Self::READ.bits() | Self::WRITE.bits() | Self::EXECUTE.bits();
    }
}
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct CompilerFlags: u32 {
        const OPTIMIZE = 1 << 0;
        const DEBUG_INFO = 1 << 1;
        const WARNINGS_AS_ERRORS = 1 << 2;
        const VERBOSE = 1 << 3;
        const LINK_TIME_OPTIMIZATION = 1 << 4;
        const STATIC_LINKING = 1 << 5;
        const PROFILE = 1 << 6;
        const RELEASE = Self::OPTIMIZE.bits() | Self::LINK_TIME_OPTIMIZATION.bits();
        const DEBUG = Self::DEBUG_INFO.bits() | Self::VERBOSE.bits();
    }
}
pub fn demonstrate_compiler_flags() {
    let debug_build = CompilerFlags::DEBUG;
    let release_build = CompilerFlags::RELEASE;

    println!("Debug flags: {:?}", debug_build);
    println!("Release flags: {:?}", release_build);

    let custom =
        CompilerFlags::OPTIMIZE | CompilerFlags::DEBUG_INFO | CompilerFlags::WARNINGS_AS_ERRORS;
    println!("Custom build flags: {:?}", custom);
    println!(
        "Custom has optimization: {}",
        custom.contains(CompilerFlags::OPTIMIZE)
    );

    let common = debug_build.intersection(release_build);
    println!("Common flags between debug and release: {:?}", common);
}
#[derive(Debug)]
pub struct CompilerOptions {
    flags: CompilerFlags,
    optimization_level: u8,
}
impl CompilerOptions {
    pub fn new(flags: CompilerFlags) -> Self {
        let optimization_level = if flags.contains(CompilerFlags::OPTIMIZE) {
            if flags.contains(CompilerFlags::LINK_TIME_OPTIMIZATION) {
                3
            } else {
                2
            }
        } else {
            0
        };

        Self {
            flags,
            optimization_level,
        }
    }

    pub fn is_debug_build(&self) -> bool {
        self.flags.contains(CompilerFlags::DEBUG_INFO)
    }

    pub fn enable_profiling(&mut self) {
        self.flags.insert(CompilerFlags::PROFILE);
    }

    pub fn optimization_level(&self) -> u8 {
        self.optimization_level
    }
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_file_permissions() {
        let perms = FilePermissions::READ | FilePermissions::WRITE;
        assert!(perms.contains(FilePermissions::READ));
        assert!(perms.contains(FilePermissions::WRITE));
        assert!(!perms.contains(FilePermissions::EXECUTE));

        assert_eq!(
            FilePermissions::ALL,
            FilePermissions::READ | FilePermissions::WRITE | FilePermissions::EXECUTE
        );
    }

    #[test]
    fn test_compiler_flags() {
        let debug = CompilerFlags::DEBUG;
        assert!(debug.contains(CompilerFlags::DEBUG_INFO));
        assert!(debug.contains(CompilerFlags::VERBOSE));
        assert!(!debug.contains(CompilerFlags::OPTIMIZE));
    }

    #[test]
    fn test_compiler_options() {
        let release_options = CompilerOptions::new(CompilerFlags::RELEASE);
        assert!(!release_options.is_debug_build());
        assert_eq!(release_options.optimization_level, 3);

        let debug_options = CompilerOptions::new(CompilerFlags::DEBUG);
        assert!(debug_options.is_debug_build());
        assert_eq!(debug_options.optimization_level, 0);
    }
}
pub fn demonstrate_file_permissions() {
    let mut perms = FilePermissions::READ | FilePermissions::WRITE;
    println!("Initial permissions: {:?}", perms);
    println!("Can read: {}", perms.contains(FilePermissions::READ));
    println!("Can execute: {}", perms.contains(FilePermissions::EXECUTE));

    perms.insert(FilePermissions::EXECUTE);
    println!("After adding execute: {:?}", perms);

    perms.remove(FilePermissions::WRITE);
    println!("After removing write: {:?}", perms);

    let readonly = FilePermissions::READ;
    let readwrite = FilePermissions::READ_WRITE;
    println!(
        "Read-only intersects with read-write: {}",
        !readonly.intersection(readwrite).is_empty()
    );
}
}

The methods insert and remove modify flag sets in place, while intersection checks for overlapping permissions. This API is much clearer than manual bit manipulation and prevents common errors like using the wrong bit mask.