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.
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.
[dependencies]
flaglet = "0.1"use flaglet::flags;
#[flags]
pub enum Permissions {
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2,
}
let mut perms = Permissions::flags(); // entry point on the enum itself
perms |= Permissions::Read;
perms |= Permissions::Write;
assert!(perms.contains(Permissions::Read));
assert!(!perms.contains(Permissions::Execute));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 flaglet::flags;
use core::mem::size_of;
#[flags]
pub enum Permission {
Read = 0b001,
Write = 0b010,
Execute = 0b100,
}
// Generates: pub struct PermissionFlags(u8);
assert!(PermissionFlags::empty().is_empty());
assert_eq!(size_of::<Permission>(), size_of::<u8>());
assert_eq!(size_of::<PermissionFlags>(), size_of::<u8>());The backing integer type is chosen automatically from the largest
discriminant value (u8, u16, u32, or u64). You can override it
explicitly:
use flaglet::flags;
use core::mem::size_of;
#[flags(u32)]
pub enum Permission {
Read = 0b001,
Write = 0b010,
Execute = 0b100,
}
// Generates: pub struct PermissionFlags(u32);
assert!(PermissionFlags::empty().is_empty());
assert_eq!(size_of::<Permission>(), size_of::<u32>());
assert_eq!(size_of::<PermissionFlags>(), size_of::<u32>());use flaglet::flags;
#[flags]
enum Permissions {
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2,
}
let _f = Permissions::flags(); // empty
let _f = Permissions::all(); // all variants set
let _f = Permissions::Read | Permissions::Write; // combine variants
let _f = PermissionsFlags::from_flag(Permissions::Read); // single variant
// Const context — use union() instead of |
const RW: PermissionsFlags = PermissionsFlags::empty()
.union(Permissions::Read)
.union(Permissions::Write);
// From a raw integer (e.g. deserialization, FFI) — unknown bits are masked out
let _f = PermissionsFlags::from_bits(0b011_u8);set, unset, contains, and contains_any accept both a single variant
and a combined flags value:
let mut f = PermissionsFlags::empty();
f.set(Permissions::Read); // set one flag
f.set(Permissions::Read | Permissions::Write); // set multiple flags
f.unset(Permissions::Write); // 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(Permissions::Read);
let _ = f.contains(Permissions::Read | Permissions::Write);
// contains_any: true if AT LEAST ONE specified bit is set
let _ = f.contains_any(Permissions::Read | Permissions::Write);
// is_disjoint: true if NO specified bits are set (opposite of contains_any)
let _ = f.is_disjoint(Permissions::Execute);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 = Permissions::Read | Permissions::Write | Permissions::Execute;
// Mutating
f |= Permissions::Read; // set
f &= Permissions::Write; // mask
f ^= Permissions::Execute; // toggle
// Non-mutating (returns a new PermissionsFlags)
let g = f | Permissions::Read;
let h = f & Permissions::Write;
let i = f ^ Permissions::Execute;
let j = !f; // invert all bits
// Flags OP Flags
let _k = f | g;
let _l = f & g;Cross-type PartialEq is implemented in both directions:
let mut f = PermissionsFlags::empty();
f.set(Permissions::Read);
assert!(f == Permissions::Read); // PermissionsFlags == Permissions
assert!(Permissions::Read == f); // Permissions == PermissionsFlagsEquality holds only when the flags value has exactly one bit set matching the variant.
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:
#[flags]
#[derive(Debug, Serialize, Deserialize, Archive)]
pub enum Permissions {
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2,
}
// Both Permissions and PermissionsFlags implement Debug, Serialize,
// Deserialize, and Archive.Because PermissionsFlags is a real struct generated in your crate's
scope, you can add your own methods to it directly:
impl PermissionsFlags {
pub fn read_write() -> Self {
Permissions::flags() | Permissions::Read | Permissions::Write
}
pub fn is_read_only(&self) -> bool {
self.contains(Permissions::Read) && !self.contains(Permissions::Write)
}
}The visibility of the enum is forwarded to both the enum and the generated flags struct:
use flaglet::flags;
#[flags] pub enum Foo { A = 1 } // pub enum Foo + pub struct FooFlags
#[flags] pub(crate) enum Bar { A = 1 } // pub(crate) for both
#[flags] enum Baz { A = 1 } // private for bothflaglet is fully no_std compatible. All generated code uses core::ops
exclusively. No feature flag required.
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.
Licensed under either of GPL-3.0-or-later or BSD-2-Clause at your option.