Skip to main content

bitflags

Macro bitflags 

Source
macro_rules! bitflags {
    (
        $( #![ $($bitflags_meta:tt)* ] )*
        $tag_name:ident;
        $( $tt:tt )*
    ) => { ... };
    (
        $( #![ $($bitflags_meta:tt)* ] )*
        $( #[ $($tag_meta:tt)* ] )*
        $tag_vis:vis struct $tag_name:ident;
        $( $tt:tt )*
    ) => { ... };
}
Expand description

Defines a flag set from a compact declaration.

The macro generates a BitFlagsSpec implementation for a tag type, along with named constants for individual bits and combined masks.

This is a convenience wrapper around the manual BitFlagsSpec implementation. The expansion is small and inspectable. Running cargo expand shows code that matches what you would write by hand.

The macro does not generate a type alias for the flag set. Type aliases are trivial to write in ordinary Rust, and generating one would create a naming asymmetry: the flag set would have a name but the individual flag would not. Write the aliases you need next to the macro invocation:

use bitflags_lite::{BitFlag, BitFlags};
bitflags_lite::bitflags! {
    #![storage(u32)]
    pub struct PermissionsTag;
    // ...
}
pub type Permissions = BitFlags<u32, PermissionsTag>;
pub type Permission = BitFlag<u32, PermissionsTag>;

§Syntax

bitflags! {
    // Storage type. Required.
    #![storage( STORAGE )]

    // Optional default name matcher.
    $( #![name_matcher( EXPR )] )?

    // Tag declaration. The `VIS struct` prefix is optional.
    $( $( #[$ATTR] )* VIS struct )? TAG;

    // Bit declarations. The name and the name slice are optional.
    $( #[$ATTR] )* VIS bit NAME = INDEX, &[NAMES];
    $( #[$ATTR] )* VIS bit NAME = INDEX;
    $( #[$ATTR] )* VIS bit      = INDEX;

    // Mask declarations. The display name is optional.
    $( #[$ATTR] )* VIS mask NAME = [ A, B, C ], "NAME";
    $( #[$ATTR] )* VIS mask NAME = EXPR, "NAME";
}

where:

  • STORAGE is a primitive integer type such as u32. It is required.
  • VIS is an optional visibility modifier such as pub or pub(crate).
  • TAG is the tag type name. Writing VIS struct TAG; generates the tag with standard derives. Writing just TAG; reuses an existing one.
  • INDEX is the zero-based bit index.
  • &[NAMES] is a slice of one or more names for the bit. The first is used by Display. All are accepted when parsing.
  • bit NAME = INDEX; declares a bit that has a named constant but no display name.
  • bit = INDEX; declares a valid bit with no constant and no name. It contributes to VALID_BITS_MASK but not to FLAG_NAMES.
  • [ A, B, C ] computes a union of named bit constants into a multi-bit value. The special syntax is required because the macro runs in const context, where BitFlag does not implement BitOr in a way that can be folded into a raw integer.
  • EXPR is a bit-mask expression, for example 0 or FIRST.mask_or_default() | SECOND.mask_or_default().
  • #![name_matcher(...)] sets BitFlagsSpec::DEFAULT_NAME_MATCHER and defaults to NameMatcher::Exact.
  • ATTR is any outer attribute, including doc comments. Attributes are copied to the generated item.

Indices that are not declared are invalid. They are excluded from VALID_BITS_MASK and cleared by truncating operations.

§Tag declaration

The tag line may be written in two ways:

ⓘ
// Reuse an existing tag type.
bitflags! {
    #![storage(u32)]
    SomeTag;
    // ...
}

// Generate the tag type.
bitflags! {
    #![storage(u32)]
    pub struct SomeTag;
    // ...
}

Attributes on the tag declaration are copied to the generated struct.

§Generated items

The macro emits impl BitFlagsSpec<STORAGE> for TAG, plus one const per named bit or mask. The tag struct is emitted only when requested. Attributes written on a declaration are copied to the corresponding generated item.

The generated code is approximately equivalent to the following pattern:

ⓘ
// Tag struct, if requested.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default, Debug)]
pub struct TAG;

// Spec implementation for the tag.
impl BitFlagsSpec<STORAGE> for TAG {
    const VALID_BITS_MASK: STORAGE = ...;
    const FLAG_NAMES: &[&[&str]] = ...;
    const COMBINED_NAMES: &[(STORAGE, &str)] = ...;
    const DEFAULT_NAME_MATCHER: NameMatcher = ...;
}

// Bit constant, one per named `bit`.
pub const NAME: BitFlag<STORAGE, TAG> =
    BitFlag::<STORAGE, TAG>::from_bit_retain(INDEX);

// Mask constant, one per named `mask`.
pub const NAME: BitFlags<STORAGE, TAG> =
    BitFlags::<STORAGE, TAG>::from_bits_retain(VALUE);

FLAG_NAMES is sized to the highest declared bit index. Gaps are filled with empty slices, so unnamed and undeclared indices coexist.

§Examples

Simple example with three consequent bits in bit flags.

use bitflags_lite::{BitFlagsSpec, NameMatcher};

bitflags_lite::bitflags! {
    #![storage(u32)]

    pub struct PermissionsTag;

    pub bit READ  = 0, &["read"];
    pub bit WRITE = 1, &["write"];
    pub bit EXEC  = 2, &["exec", "execute"];

    pub mask RWX  = [ READ, WRITE, EXEC ], "all";
    pub mask NONE = 0, "none";
}

assert_eq!(PermissionsTag::VALID_BITS_MASK, 0b111);
assert_eq!(
    PermissionsTag::FLAG_NAMES,
    &[&["read"][..], &["write"], &["exec", "execute"]]
);
assert_eq!(
    PermissionsTag::COMBINED_NAMES,
    &[(0b111, "all"), (0, "none")]
);
assert_eq!(PermissionsTag::DEFAULT_NAME_MATCHER, NameMatcher::Exact);

assert_eq!(READ.bit(), 0);
assert_eq!(WRITE.bit(), 1);
assert_eq!(EXEC.bit(), 2);
assert_eq!(RWX.bits(), 0b111);
assert_eq!(NONE.bits(), 0);
Macro expansion (click to show)
use bitflags_lite::{BitFlagsSpec, NameMatcher};

#[derive(
    ::core::marker::Copy,
    ::core::clone::Clone,
    ::core::cmp::Eq,
    ::core::cmp::PartialEq,
    ::core::cmp::Ord,
    ::core::cmp::PartialOrd,
    ::core::hash::Hash,
    ::core::default::Default,
    ::core::fmt::Debug,
)]
pub struct PermissionsTag;

impl bitflags_lite::BitFlagsSpec<u32> for PermissionsTag {
    const VALID_BITS_MASK: u32 =
        const { (0 as u32) | (1 as u32) << 0 | (1 as u32) << 1 | (1 as u32) << 2 };
    const FLAG_NAMES: &[&[&str]] = const {
        const MAX: usize = bitflags_lite::util::__const_max(&[0, 1, 2]);
        const FLAG_NAMES: [&'static [&'static str]; MAX + 1] = const {
            let mut flag_names: [&'static [&'static str]; MAX + 1] = [&[]; MAX + 1];
            flag_names[0] = (&["read"]);
            flag_names[1] = (&["write"]);
            flag_names[2] = (&["exec", "execute"]);
            flag_names
        };
        &FLAG_NAMES
    };
    const COMBINED_NAMES: &[(u32, &str)] = &[
        (
            (0 | bitflags_lite::BitFlag::<u32, PermissionsTag>::mask_or_default(&READ)
                | bitflags_lite::BitFlag::<u32, PermissionsTag>::mask_or_default(&WRITE)
                | bitflags_lite::BitFlag::<u32, PermissionsTag>::mask_or_default(&EXEC)),
            "all",
        ),
        (0, "none"),
    ];
    const DEFAULT_NAME_MATCHER: bitflags_lite::NameMatcher =
        (bitflags_lite::NameMatcher::Exact);
}

pub const READ: bitflags_lite::BitFlag<u32, PermissionsTag> =
    bitflags_lite::BitFlag::<u32, PermissionsTag>::from_bit_retain(0);

pub const WRITE: bitflags_lite::BitFlag<u32, PermissionsTag> =
    bitflags_lite::BitFlag::<u32, PermissionsTag>::from_bit_retain(1);

pub const EXEC: bitflags_lite::BitFlag<u32, PermissionsTag> =
    bitflags_lite::BitFlag::<u32, PermissionsTag>::from_bit_retain(2);

pub const RWX: bitflags_lite::BitFlags<u32, PermissionsTag> =
    bitflags_lite::BitFlags::<u32, PermissionsTag>::from_bits_retain(
        (0 | bitflags_lite::BitFlag::<u32, PermissionsTag>::mask_or_default(&READ)
            | bitflags_lite::BitFlag::<u32, PermissionsTag>::mask_or_default(&WRITE)
            | bitflags_lite::BitFlag::<u32, PermissionsTag>::mask_or_default(&EXEC)),
    );

pub const NONE: bitflags_lite::BitFlags<u32, PermissionsTag> =
    bitflags_lite::BitFlags::<u32, PermissionsTag>::from_bits_retain(0);

Example with invalid and unnamed but valid bits.

use bitflags_lite::{BitFlagsSpec, NameMatcher};

bitflags_lite::bitflags! {
    #![storage(u32)]
    #![name_matcher(NameMatcher::AsciiCaseInsensitive)]

    /// Flags tag doc-comment.
    pub struct FlagsTag;

    /// `FIRST` `BitFlag` doc-comment.
    pub bit FIRST = 0, &["first", "1st"];

    /// `THIRD` `BitFlag` doc-comment.
    pub bit THIRD = 2, &["third", "3rd"];

    pub bit       = 3;

    /// `TENTH` `BitFlag` doc-comment.
    pub bit TENTH = 9, &["tenth", "10th"];

    /// `ALL` `BitFlags` doc-comment.
    pub mask ALL     = [ FIRST, THIRD, TENTH ], "all";

    /// `ALL_ALT` `BitFlags` doc-comment.
    pub mask ALL_ALT = FIRST.mask_or_default()
                     | THIRD.mask_or_default()
                     | TENTH.mask_or_default();

    /// `NONE` `BitFlags` doc-comment.
    pub mask NONE    = 0, "none";
}

assert_eq!(FlagsTag::VALID_BITS_MASK, 0b10_0000_1101);
assert_eq!(
    FlagsTag::FLAG_NAMES,
    &[
        &["first", "1st"][..],
        &[],
        &["third", "3rd"],
        &[],
        &[],
        &[],
        &[],
        &[],
        &[],
        &["tenth", "10th"]
    ]
);
assert_eq!(
    FlagsTag::COMBINED_NAMES,
    &[(0b10_0000_0101, "all"), (0, "none")]
);
assert_eq!(
    FlagsTag::DEFAULT_NAME_MATCHER,
    NameMatcher::AsciiCaseInsensitive
);

assert_eq!(FIRST.bit(), 0);
assert_eq!(THIRD.bit(), 2);
assert_eq!(TENTH.bit(), 9);
assert_eq!(ALL.bits(), 0b10_0000_0101);
assert_eq!(NONE.bits(), 0);
Macro expansion (click to show)
use bitflags_lite::{BitFlagsSpec, NameMatcher};

#[doc = r" Flags tag doc-comment."]
#[derive(
    ::core::marker::Copy,
    ::core::clone::Clone,
    ::core::cmp::Eq,
    ::core::cmp::PartialEq,
    ::core::cmp::Ord,
    ::core::cmp::PartialOrd,
    ::core::hash::Hash,
    ::core::default::Default,
    ::core::fmt::Debug,
)]
pub struct FlagsTag;

impl bitflags_lite::BitFlagsSpec<u32> for FlagsTag {
    const VALID_BITS_MASK: u32 = const {
        (0 as u32) | (1 as u32) << 0 | (1 as u32) << 2 | (1 as u32) << 3 | (1 as u32) << 9
    };
    const FLAG_NAMES: &[&[&str]] = const {
        const MAX: usize = bitflags_lite::util::__const_max(&[0, 2, 3, 9]);
        const FLAG_NAMES: [&'static [&'static str]; MAX + 1] = const {
            let mut flag_names: [&'static [&'static str]; MAX + 1] = [&[]; MAX + 1];
            flag_names[0] = (&["first", "1st"]);
            flag_names[2] = (&["third", "3rd"]);
            flag_names[9] = (&["tenth", "10th"]);
            flag_names
        };
        &FLAG_NAMES
    };
    const COMBINED_NAMES: &[(u32, &str)] = &[
        (
            (0 | bitflags_lite::BitFlag::<u32, FlagsTag>::mask_or_default(&FIRST)
                | bitflags_lite::BitFlag::<u32, FlagsTag>::mask_or_default(&THIRD)
                | bitflags_lite::BitFlag::<u32, FlagsTag>::mask_or_default(&TENTH)),
            "all",
        ),
        (0, "none"),
    ];
    const DEFAULT_NAME_MATCHER: bitflags_lite::NameMatcher =
        (NameMatcher::AsciiCaseInsensitive);
}

#[doc = r" `FIRST` `BitFlag` doc-comment."]
pub const FIRST: bitflags_lite::BitFlag<u32, FlagsTag> =
    bitflags_lite::BitFlag::<u32, FlagsTag>::from_bit_retain(0);

#[doc = r" `THIRD` `BitFlag` doc-comment."]
pub const THIRD: bitflags_lite::BitFlag<u32, FlagsTag> =
    bitflags_lite::BitFlag::<u32, FlagsTag>::from_bit_retain(2);

#[doc = r" `TENTH` `BitFlag` doc-comment."]
pub const TENTH: bitflags_lite::BitFlag<u32, FlagsTag> =
    bitflags_lite::BitFlag::<u32, FlagsTag>::from_bit_retain(9);

#[doc = r" `ALL` `BitFlags` doc-comment."]
pub const ALL: bitflags_lite::BitFlags<u32, FlagsTag> =
    bitflags_lite::BitFlags::<u32, FlagsTag>::from_bits_retain(
        (0 | bitflags_lite::BitFlag::<u32, FlagsTag>::mask_or_default(&FIRST)
            | bitflags_lite::BitFlag::<u32, FlagsTag>::mask_or_default(&THIRD)
            | bitflags_lite::BitFlag::<u32, FlagsTag>::mask_or_default(&TENTH)),
    );

#[doc = r" `ALL_ALT` `BitFlags` doc-comment."]
pub const ALL_ALT: bitflags_lite::BitFlags<u32, FlagsTag> =
    bitflags_lite::BitFlags::<u32, FlagsTag>::from_bits_retain(
        (FIRST.mask_or_default() | THIRD.mask_or_default() | TENTH.mask_or_default()),
    );

#[doc = r" `NONE` `BitFlags` doc-comment."]
pub const NONE: bitflags_lite::BitFlags<u32, FlagsTag> =
    bitflags_lite::BitFlags::<u32, FlagsTag>::from_bits_retain(0);