flaglet 🏳️
A proc-macro attribute that turns a plain Rust enum into a fully-featured typed bitflag API — without a DSL, without a wrapper type you don't own, and with first-class support for any derivable trait.
Why flaglet?
flaglet exists to stay out of your way — one attribute, and the rest is plain Rust.
- Plain enum syntax. Just annotate a regular enum — no custom DSL, no derive macro, no boilerplate. Your variants stay ordinary Rust.
- A named companion type you own.
#[flags]onPermissionsenum generatesPermissionsFlags: a concrete struct in your crate that you canimpl. - Derive forwarding. Any
#[derive(...)]placed on the enum is automatically forwarded to both the enum and the generated flags struct. - Auto-sized backing integer. The macro picks the smallest integer type
that fits your variants; override it with
#[flags(u64)]when needed. no_stdout of the box. No allocator required.
Installation
[]
= "0.1"
Quickstart
use flags;
let mut perms = flags; // entry point on the enum itself
perms |= Read;
perms |= Write;
assert!;
assert!;
Usage
The #[flags] attribute
Apply #[flags] to any enum whose variants are unit variants with explicit
power-of-2 discriminants. The macro generates a companion struct named
{EnumName}Flags in the same scope.
use flags;
use size_of;
// Generates: pub struct PermissionFlags(u8);
assert!;
assert_eq!;
assert_eq!;
The backing integer type is chosen automatically from the largest
discriminant value (u8, u16, u32, or u64). You can override it
explicitly:
use flags;
use size_of;
// Generates: pub struct PermissionFlags(u32);
assert!;
assert_eq!;
assert_eq!;
Constructing a flags value
use flags;
let _f = flags; // empty
let _f = all; // all variants set
let _f = Read | Write; // combine variants
let _f = from_flag; // single variant
// Const context — use union() instead of |
const RW: PermissionsFlags = empty
.union
.union;
// From a raw integer (e.g. deserialization, FFI) — unknown bits are masked out
let _f = from_bits;
Setting and testing flags
set, unset, contains, and contains_any accept both a single variant
and a combined flags value:
let mut f = empty;
f.set; // set one flag
f.set; // set multiple flags
f.unset; // clear one or more flags
let _ = f.is_empty; // true if no flags are set
let _ = f.bits; // raw integer value
// contains: true if ALL specified bits are set
let _ = f.contains;
let _ = f.contains;
// contains_any: true if AT LEAST ONE specified bit is set
let _ = f.contains_any;
// is_disjoint: true if NO specified bits are set (opposite of contains_any)
let _ = f.is_disjoint;
Bitwise operators
All standard bitwise operators are implemented between the enum and the flags struct, in both mutating and non-mutating forms:
// Enum OP Enum → PermissionsFlags (chains freely)
let mut f = Read | Write | Execute;
// Mutating
f |= Read; // set
f &= Write; // mask
f ^= Execute; // toggle
// Non-mutating (returns a new PermissionsFlags)
let g = f | Read;
let h = f & Write;
let i = f ^ Execute;
let j = !f; // invert all bits
// Flags OP Flags
let _k = f | g;
let _l = f & g;
Equality
Cross-type PartialEq is implemented in both directions:
let mut f = empty;
f.set;
assert!; // PermissionsFlags == Permissions
assert!; // Permissions == PermissionsFlags
Equality holds only when the flags value has exactly one bit set matching the variant.
Derive forwarding
Any #[derive(...)] on the enum is forwarded to both the enum and the
generated flags struct. This includes third-party derives like
serde::Serialize or rkyv::Archive — no stubs, no wrappers:
// Both Permissions and PermissionsFlags implement Debug, Serialize,
// Deserialize, and Archive.
Extending the generated type
Because PermissionsFlags is a real struct generated in your crate's
scope, you can add your own methods to it directly:
Visibility
The visibility of the enum is forwarded to both the enum and the generated flags struct:
use flags;
// pub enum Foo + pub struct FooFlags
pub // pub(crate) for both
// private for both
no_std
flaglet is fully no_std compatible. All generated code uses core::ops
exclusively. No feature flag required.
Compile-time validation
The macro rejects invalid inputs with precise error messages pointing at the offending token:
- Variants with fields (tuple or struct variants) are rejected.
- Variants without an explicit discriminant are rejected.
- Discriminants that are zero or not a power of 2 are rejected.
- Discriminants that exceed
u64::MAXare rejected. - Discriminant expressions must be integer literals or
1 << Nform.
License
Licensed under either of GPL-3.0-or-later or BSD-2-Clause at your option.