A future for bitflags
I’ve been helping maintain the bitflags crate for a long time now. One of the first things I did when inheriting the library was release a 2.0 that tried to tighten up the internals a bit. The issue was mostly around how generated code behaved with #[derive]s. When you wrote:
bitflags! {
#[derive(Serialize, Deserialize)]
pub struct MyFlags: u32 {
const A = 0b01;
const B = 0b10;
}
}
you’d end up with a generated type that looked like this:
#[derive(Serialize, Deserialize)]
pub struct MyFlags(u32);
The #[derive]s would see the underlying bits field as an integer, with no awareness that it was a set of flags. So serializing MyFlags::A | MyFlags::B would give you 3 instead of "A | B", for example.
In 2.0, we’d generate a hidden internal type instead:
#[derive(Serialize, Deserialize)]
pub struct MyFlags(__InternalMyFlags);
where __InternalMyFlags would only implement Serialize (and so support #[derive]s on containers using it) when a serde feature of bitflags was enabled, using a flags-aware implementation.
I think this behaviour has been largely a nuisance for users, and is also a bit of a dead-end. It means bitflags needs to directly depend on any library you want to #[derive], which is becoming less appealing as the Rust ecosystem continues to grow, and supply chain security becomes more critical.
The internals of the existing bitflags crate are also very complicated. It’s a big ball of macro_rules! macros that are difficult to maintain, and difficult to extend.
So that direction hasn’t quite panned out, but I think there are a few things we added that are a stronger foundation to build off going forwards:
- The
Flagstrait: A trait for both reflecting over defined flags, and for working with an instance of a flags value. All of the external integration inbitflags, serialization, generation, formatting, parsing, is all built off this trait. - The spec: An attempt to fully specify the terminology and behaviour of flags types. It outlines the way
bitflagshas previously tried to deal with issues of “unknown bits” (any bits set in a value that don’t correspond to a defined flag), and flags that occupy multiple bits, both of which have been fairly adhoc. I think it’s a good starting point for any attempt to introduce flags-like functionality as a language feature in the future.
Recently I’ve spun up a new bitflags-derive library. It’s a proc macro library for adding flags-aware #[derive]s to types generated by the main bitflags crate using the Flags trait. So far it supports things like this:
bitflags! {
#[derive(FlagsSerialize, FlagsDeserialize)]
pub struct MyFlags: u32 {
const A = 0b01;
const B = 0b10;
}
}
The FlagsSerialize and FlagsDeserialize are flags-aware implementations of Serialize and Deserialize that don’t require bitflags to directly depend on serde.
Going forwards, any integration with external libraries will live in bitflags-derive. I also plan to put together an alternative proc-macro version of the bitflags! macro in there that can support its own set of attributes for some of the codegen features uesrs have been asking for. Things like renaming flags, auto-generating values, etc.
So what does that mean for bitflags? Nothing. I’ll keep maintaining it as I have been. No major versions or breakage are planned. If I did any 3.0 (which I’m not planning) all it would do is remove the existing external integrations in favour of bitflags-derive. There are plenty of users who don’t want to depend on proc-macros, and I think that’s fair enough.