bitflags: a Rust macro for flag types that admit their own undefined bits
A macro to generate structures which behave like bitflags
At a glance
- What is it?
- bitflags is a Rust macro that generates flag structures with set algebra, optional serde and bytemuck support, and a documented specification, but it deliberately cannot keep undefined bits out of a value. This looks at what the generated type does, what it refuses to do, and where a hand written struct is the better answer.
- Who is it for?
- bitflags fits a Rust project that passes around sets of options, wraps a C API whose flags are not fully known in advance, or wants an options type with parsing and formatting support, and it fits no-std targets because the crate carries a `std` feature you can leave off.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
It generates a struct, not an enum you can match on
The project description calls it a macro to generate structures which behave like bitflags, and that phrase is the key. A bitflags type is not a Rust enum. It cannot be matched exhaustively with `match`, and it is not a closed set of values. It is a newtype around an integer where each named constant is a bit pattern, and where the type implements union, intersection, difference and complement as operators.
The documentation gives two reasons to reach for it. The first is providing more user-friendly bindings to C APIs where flags may or may not be fully known in advance. The second is generating efficient options types with string parsing and formatting support.
The first of those is the reason the crate exists in most codebases. A C header exposes an `int` where each bit means something, and the Rust side is left with magic numbers and manual bit twiddling. bitflags turns that integer into a named type with a documented set of constants, so a caller writes `Flags::READ | Flags::WRITE` instead of `1 | 2`, and a reviewer can see the intent.
The second reason is the reason some projects adopt it later. An options type with parsing and formatting turns configuration strings into a typed value, which is a different job from bit twiddling and uses the same machinery.
Set algebra on the generated type
The example in the project documentation is the clearest statement of the semantics. A flags structure is declared with the `bitflags!` macro, naming the underlying storage type after a colon, and then listing constants with their bit positions:
use bitflags::bitflags;
bitflags! {
/// Represents a set of flags.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Flags: u32 {
/// The value `A`, at bit position `0`.
const A = 0b00000001;
/// The value `B`, at bit position `1`.
const B = 0b00000010;
/// The value `C`, at bit position `2`.
const C = 0b00000100;
/// The combination of `A`, `B`, and `C`.
const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits();
}
}The operations then follow from that representation. The documentation demonstrates union with `|`, intersection with `&`, set difference with `-`, and set complement with `!`, asserting that `Flags::A | Flags::C` combined with `Flags::B | Flags::C` yields `Flags::ABC`, that the intersection of those two is `Flags::C`, that the difference is `Flags::A`, and that the complement of the second is `Flags::A`.
Two details are worth pausing on. The derives are written by you, not chosen by the macro, so the generated type gets exactly the traits you ask for and no more. And the `bits()` accessor is what makes composite constants like `ABC` writable in the declaration itself, which means the macro does not have to hard code a naming convention for combinations.
Adding it to a project and generating the first type
The dependency is a normal Cargo entry, with the version pinned in `Cargo.toml`:
[dependencies]
bitflags = "2.13.2"In the source file, the macro is imported by name:
use bitflags::bitflags;That is the whole setup. There is no build script, no code generation step, and no separate crate to enable beyond the import.
The Cargo features are the opt-in list, and they are small. `std` implements the `Error` trait on the error types used by bitflags, and leaving it off keeps the crate usable in a no-std environment, which is why the package metadata lists `no-std` among its categories. `serde` supports deriving serde traits on the generated flags types, `arbitrary` supports deriving arbitrary traits, and `bytemuck` supports deriving bytemuck traits, which matters when the type has to be cast to raw bytes for FFI or GPU buffers.
The manifest in the repository shows how those features are wired. The `std` feature is empty and exists to switch behaviour on, `serde` pulls in an optional `serde_core` dependency, and `arbitrary` and `bytemuck` appear as optional dependencies. There is also an `example_generated` feature that the docs.rs metadata enables, so the published documentation shows the code the macro produces rather than just the macro invocation.
spec.md is the contract the macro implements
The project publishes a specification document, `spec.md`, alongside the crate, and it is linked from the main documentation as the reference for the generated API. That is unusual for a macro crate, and it changes how you should read the docs.
With most macro crates, the behaviour of the generated code is whatever the macro happens to do, and the documentation is prose describing it. With bitflags, there is a written specification of what a conforming flags type exposes, and the macro is an implementation of it. If you are writing code that consumes a bitflags type, or writing a macro of your own that should behave the same way, that document is the place to start rather than the source.
The repository also has a separate `bitflags_derive` project, linked from the README as the home of other flags-aware traits.
The documentation pages live on docs.rs, and release notes are published as GitHub releases. The changelog is kept in `CHANGELOG.md` in the repository root.
It cannot keep undefined bits out, and it is not a bitfield
The project is unusually candid about what it will not do, and those two limitations are the ones to read before you commit to the type.
The first is that bitflags cannot guarantee only bits corresponding to defined flags will ever be set. The documentation explains why: bitflags allows access to the underlying bits type, so arbitrary bits may be set. That access is what makes the `bits()` accessor work, and it is also what makes composite constants writable inside the declaration. The consequence is that a value produced from a C API, from a wire format, or from a raw integer can contain bits your type has no name for, and your match arms will not catch them.
The second is that bitflags cannot define bitfields. The generated types only model set bits as denoting the presence of some combination of flags. A bitfield is the opposite: a set bit means a stored value occupying that field, with the width of the field carrying meaning. If you are modelling a register layout with a three bit mode field and a five bit count field, this macro is the wrong shape entirely, and the documentation says so plainly rather than leaving you to discover it.
There is a third, smaller edge. The two use cases the project lists, user-friendly bindings to C APIs and options types with string parsing and formatting, are what the crate is tuned for. A flags type used as a general bit bag with no named constants gains little over a plain integer with a newtype.
A hand written struct, and the examples folder as evidence
The repository ships an `examples/` directory whose contents say more about the design space than the prose does. There is `custom_bits_type.rs`, for using a custom integer type as the underlying storage. There is `custom_derive.rs`, for controlling which derives are applied. There is `fmt.rs`, about formatting, and `serde.rs`, about the serde support. And there is `macro_free.rs`, which is the interesting one: an example of the same functionality written without the macro.
That last file is the alternative worth understanding, because it is not a different library, it is the same API expressed by hand. The difference in approach is that the macro saves you from writing the set algebra, the `bits` accessor, the constant definitions, and the trait implementations by hand every time, at the cost of the code being invisible to the reader and to the compiler until expansion. The hand written version is longer but greppable and debuggable, and for a type used in exactly one place, it is often the better trade.
The `custom_bits_type.rs` example also shows that the underlying type is not fixed to a primitive. If your flags are wider than 64 bits, or you need a big integer, the example shows the path. The generated type is a wrapper, and the wrapper can wrap something other than `u32`.
There are also `benches/` and `tests/` directories, with `trybuild` in the dev dependencies, which is the standard way to test the output of a macro by compiling expected failures and comparing the compiler messages.
MIT or Apache-2.0, and a fast moving minor version line
The licence is dual, declared in the manifest as `MIT OR Apache-2.0`, with the two texts in `LICENSE-MIT` and `LICENSE-APACHE` at the repository root. For a crate that ends up inside a large application, the dual licence means you take whichever of the two suits your distribution model, and both are permissive. There is no copyleft obligation on code that merely uses a flags type.
The version line moves quickly. 2.13.2 was released on 2026-09-10, 2.13.1 on 2026-07-15, and 2.13.0 on 2026-06-05, and the last push to the repository was on 2026-10-17. So the project is taking minor releases roughly every month to two, which for a crate of this age and stability is a sign of active work rather than churn.
The one upgrade concern the documentation raises is the minimum supported Rust version. The manifest currently declares `rust-version = "1.56.0"` and the README notes that the minimum supported Rust version is documented in the `Cargo.toml` file and may be bumped in minor releases as necessary. That is a promise and a warning at the same time: your build can be broken by a dependency update rather than by your own code, and the mitigation is to watch the `rust-version` field when you bump the bitflags dependency.
The repository also carries a `SECURITY.md`, a `CODE_OF_CONDUCT.md` and a `CONTRIBUTING.md`, so the usual governance files are present for a crate that many projects depend on indirectly.
Editorial conclusion
bitflags fits a Rust project that passes around sets of options, wraps a C API whose flags are not fully known in advance, or wants an options type with parsing and formatting support, and it fits no-std targets because the crate carries a `std` feature you can leave off. It does not fit a case where the set of valid bits is closed and must be enforced, since arbitrary bits can still be set through the underlying bits type, and it does not fit bitfield semantics, where a set bit means a stored value rather than the presence of a flag. Before you adopt it, read `spec.md`, which is the document the generated API is meant to match, and check the `rust-version` in `Cargo.toml`, since the minimum supported Rust version may be raised in a minor release.
Frequently asked questions
How do I add bitflags to a Rust project?
Add `bitflags = "2.13.2"` to the `[dependencies]` section of your `Cargo.toml`, then import the macro in your source with `use bitflags::bitflags;`. The current crate version is 2.13.2, published to crates.io.
Can bitflags guarantee that only defined flag bits are set?
No. The project documentation states that bitflags allows access to the underlying bits type, so arbitrary bits may be set, and it cannot guarantee that only bits corresponding to defined flags will ever be set. Any check for unknown bits has to be done by your own code.
Does bitflags support no-std environments?
Yes, the std feature is opt-in and implements the Error trait on the error types used by bitflags, so leaving it off keeps the crate usable without the standard library. The package metadata lists no-std among its categories on crates.io.
Can bitflags be used to define a bitfield?
No. The documentation states that bitflags only generates types where set bits denote the presence of some combination of flags, and it cannot define bitfields. A layout where a set bit means a stored value with a meaningful field width needs a different design.
Which Rust version does bitflags require?
The manifest currently declares rust-version = "1.56.0". The project notes that the minimum supported Rust version is documented in the Cargo.toml file and may be bumped in minor releases as necessary, so it is worth checking that field when updating the dependency.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bitflags-bitflags)Was this useful?