---
title: How to use bitflags crate in Rust bit flags
summary: Define a type-safe bitfield in Rust using the bitflags! macro to manage multiple boolean states as a single integer value.
lang: en
slug: how-to-use-bitflags-crate-in-rust-bit-flags
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-use-bitflags-crate-in-rust-bit-flags/
---

# How to use bitflags crate in Rust bit flags



## When integers lose their meaning

You parse a configuration file. The `mode` field arrives as the integer `5`. You know `5` means read and execute. You write a helper: `if mode & 2 != 0`. It works. A teammate refactors the constants and changes the write flag to `4`. Your check breaks silently. The compiler never complained. To the compiler, `5` is just a number. It has no idea that `5` represents permissions and `4` represents write access. You shipped a bug where write access vanishes without a trace.

Raw integers carry no semantic information. The compiler happily lets you pass a `Color` integer to a function expecting `Permissions`. You end up checking the wrong bits and shipping a bug that only appears when the bit layouts collide. You need a wrapper that speaks the language of flags, not just raw bits. The `bitflags` crate provides exactly that. It generates a type-safe struct that wraps an integer, enforces valid flag combinations, and gives you ergonomic operators. The compiler stops you from mixing unrelated flag types. The macro writes the boilerplate so you don't have to.

## The concept: typed switch panels

Think of a bit flag as a row of light switches on a control panel. Each switch controls one feature. The panel itself is just a number representing the state of all switches combined. In raw code, that number is a `u8` or `u32`. It tells you nothing about what the switches do. You could hand a kitchen switch panel to the garage door opener, and the wiring might even match by accident.

`bitflags` gives you a typed panel. The generated struct is distinct from the underlying integer. You cannot hand a `Permissions` panel to a function expecting `Color`. The compiler enforces that you are talking about the right switches. The macro also implements the bitwise operators so you can combine and check flags using familiar syntax. You get the efficiency of bitwise operations with the safety of the type system.

## Minimal example

Here's the smallest case: a flag type, a combination, and a check.

```rust
use bitflags::bitflags;

/// File access permissions packed into a single byte.
/// The macro generates a struct that wraps a u8.
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Permissions: u8 {
        const READ    = 0b0001;
        const WRITE   = 0b0010;
        const EXECUTE = 0b0100;
    }
}

fn main() {
    // Combine flags with bitwise OR.
    // The macro implements BitOr, so this returns a Permissions.
    let rw = Permissions::READ | Permissions::WRITE;

    // Check if a flag is present.
    // contains() returns true if all bits in the argument are set.
    assert!(rw.contains(Permissions::READ));
    assert!(!rw.contains(Permissions::EXECUTE));

    // Check multiple flags at once.
    // This requires both READ and WRITE to be set.
    assert!(rw.contains(Permissions::READ | Permissions::WRITE));
}
```

The macro generates the rest. Focus on the flags, not the implementation.

## How the macro generates code

The `bitflags!` macro expands into a struct that wraps the integer. It implements traits like `BitOr`, `BitAnd`, `BitXor`, and `Not` so you can use `|`, `&`, `^`, and `!`. It also adds helper methods like `contains`, `intersects`, `insert`, `remove`, and `toggle`. The generated type is `#[repr(transparent)]` over the underlying integer. This means it has the exact same memory layout as the raw integer. This is crucial for FFI and serialization. The compiler can pass the flag struct directly to C functions expecting an integer without any conversion overhead.

Convention aside: always derive `Debug`, `Clone`, `Copy`, `PartialEq`, and `Eq` inside the macro block. `Clone` and `Copy` make flags cheap to pass around. `Debug` enables pretty printing. `PartialEq` and `Eq` allow comparison. These are standard conventions for flag types. You almost always want them.

The compiler can pass the flag struct directly to C functions expecting an integer without any conversion overhead.

## Realistic usage: validation and construction

Real code often involves validation and dynamic construction. Here's how to handle invalid combinations and external data.

```rust
use bitflags::bitflags;

bitflags! {
    /// Flags controlling how a file is opened.
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct OpenFlags: u32 {
        const READ     = 0x01;
        const WRITE    = 0x02;
        const CREATE   = 0x04;
        const TRUNCATE = 0x08;
    }
}

/// Validates flags before opening a file.
/// Ensures WRITE implies CREATE.
fn open_file(path: &str, flags: OpenFlags) -> Result<(), String> {
    // Check for invalid combinations.
    // contains() checks if all bits in the argument are set.
    if flags.contains(OpenFlags::WRITE) && !flags.contains(OpenFlags::CREATE) {
        return Err("WRITE requires CREATE flag".to_string());
    }

    // Check if any write-related flags are set.
    // intersects() returns true if any bits overlap.
    let write_mask = OpenFlags::WRITE | OpenFlags::TRUNCATE;
    if flags.intersects(write_mask) {
        println!("Opening '{}' with write access.", path);
    } else {
        println!("Opening '{}' read-only.", path);
    }

    Ok(())
}
```

Validate early. If the flags don't make sense, reject them before you touch the file system.

```rust
fn main() {
    // Build flags dynamically.
    // empty() creates a flag set with no bits set.
    let mut flags = OpenFlags::empty();
    flags.insert(OpenFlags::READ);
    flags.insert(OpenFlags::WRITE);
    flags.insert(OpenFlags::CREATE);

    // Valid combination.
    open_file("data.txt", flags).unwrap();

    // Construct from raw bits when parsing external data.
    // from_bits() returns None if unknown bits are set.
    let raw_from_network: u32 = 0x07;
    match OpenFlags::from_bits(raw_from_network) {
        Some(valid_flags) => {
            open_file("remote.txt", valid_flags).unwrap();
        }
        None => {
            println!("Received invalid flags: 0x{:08x}", raw_from_network);
        }
    }
}
```

## Extending the generated type

The macro allows you to inject methods directly into the generated struct. This keeps domain logic close to the definition. You can use `self` to access the flag set. This is how you encapsulate complex checks without scattering helper functions.

```rust
use bitflags::bitflags;

bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Permissions: u8 {
        const READ    = 0x01;
        const WRITE   = 0x02;
        const EXECUTE = 0x04;
    }

    // Custom method added to the generated struct.
    // Checks if the user has read-write access.
    fn is_read_write(&self) -> bool {
        self.contains(Permissions::READ | Permissions::WRITE)
    }
}

fn main() {
    let perms = Permissions::READ | Permissions::WRITE;
    // Use the custom method directly on the flag set.
    assert!(perms.is_read_write());
}
```

This is how you encapsulate complex checks without scattering helper functions.

## Key methods and patterns

The generated type provides a rich API. `contains` checks if all bits in the argument are set. `intersects` checks if any bits overlap. `all` is an alias for `contains`. `any` is an alias for `intersects`. `insert` sets bits. `remove` clears bits. `toggle` flips bits. `set` sets a flag to true or false based on a boolean. `is_empty` checks if no bits are set. `is_all` checks if all defined bits are set.

Convention aside: use hex constants like `0x01` rather than binary like `0b0001` unless the bit position is the primary mental model. Hex is the community standard for flag values. It reads cleaner and aligns with how most tools display flags.

Convention aside: use `from_bits` when receiving data from untrusted sources. It validates that no unknown bits are set. Use `from_bits_truncate` only when you trust the source or explicitly want to ignore extra bits. `from_bits_truncate` drops unknown bits and returns the flags. It is safe but can hide errors.

Trust `from_bits` for external data. If the bits don't match your definition, something is wrong.

## Pitfalls and compiler errors

The macro does not check for overlapping bits. If you define `READ = 0x01` and `WRITE = 0x01`, the macro will compile. You will get silent bugs where setting write also sets read. You must ensure each flag is a power of two and unique. The compiler trusts your definitions.

If you try to pass a raw integer where a flag type is expected, the compiler rejects you with E0308 (mismatched types). This is a feature. The type system protects you from accidental misuse. Use `.bits()` to extract the raw integer when you need to talk to C or serialize. Use `from_bits` to go back.

A common logic error is confusing `contains` and `intersects`. `flags.contains(READ | WRITE)` requires both flags to be set. `flags.intersects(READ | WRITE)` requires at least one. If you check `contains` when you meant `intersects`, you might reject valid combinations. Read the method names carefully.

Another pitfall is the empty flag trap. Flags are never `None`. `empty()` is the zero state. This is a conceptual shift for people used to `Option`. A flag set with no bits set is still a valid value. It represents "no permissions" or "no options", not "missing data".

Treat your flag definitions as a contract. If the bits overlap, the contract is broken. Verify your constants manually or add a test that checks for uniqueness.

## Decision matrix

Use `bitflags` when you need a type-safe set of boolean options packed into a single integer. Use `bitflags` when you are interfacing with C libraries that expect flag masks. Use `bitflags` when you want ergonomic operators like `|` and `&` without writing trait implementations yourself. Reach for a standard `enum` when the options are mutually exclusive and you only ever need one at a time. Reach for a struct of booleans when the flags are independent, the count is small, and you prioritize readability over memory density. Reach for raw integers only at FFI boundaries or serialization layers where the type wrapper would break the contract.

Define your flags once. Let the macro handle the rest. If you find yourself writing `if flags & 0x04 != 0`, you have already lost. Use the type.

## Where to go next

- [Understanding enums](/en/understanding-enums)
- [Writing macros](/en/writing-macros)
- [FFI basics](/en/ffi-basics)