Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Bitmask Types

Bitmasks are mapped to newtype structs in Rust, where each value is an associated constant of the newtype. This effectively scopes the value of each constant into the bitmask type, and prevents doing bitwise operations with different types. The underlying integer can be accessed through the bits function.

The implementation uses a bitmask! macro to generate the type and all required trait implementations. The generated API is equivalent to the expanded form shown below.

Bitmasks are always trivial (they wrap a primitive integer) and have total order, so they derive Copy, Debug, Eq, Ord, and Hash. See Derived Traits for the general trait derivation rules.

The #[repr(transparent)] attribute guarantees that the newtype has the same memory layout as the underlying integer type. This ensures ABI compatibility when passing bitmasks across FFI boundaries and allows safe transmutation between the bitmask and its underlying type.

// IDL
@bit_bound(32)
bitmask MyBitmask {
    A,
    B,
    @position(5) C
};
#![allow(unused)]
fn main() {
// Rust
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
pub struct MyBitmask(u32);

impl MyBitmask {
    pub const A: Self = Self(1 << 0);
    pub const B: Self = Self(1 << 1);
    pub const C: Self = Self(1 << 5);

    #[inline]
    pub const fn nil() -> Self {
        Self(0)
    }

    #[inline]
    pub const fn all() -> Self {
        Self(Self::A.0 | Self::B.0 | Self::C.0)
    }

    #[inline]
    pub const fn bits(&self) -> u32 {
        self.0
    }

    #[inline]
    pub const fn is_empty(&self) -> bool {
        self.0 == 0
    }

    #[inline]
    pub const fn contains(&self, rhs: Self) -> bool {
        (self.0 & rhs.0) == rhs.0
    }

    #[inline]
    pub fn clear(&mut self) {
        self.0 = 0
    }
}

impl ::std::ops::BitOr for MyBitmask {
    type Output = Self;

    #[inline]
    fn bitor(self, rhs: Self) -> Self::Output {
        Self(self.0 | rhs.0)
    }
}

impl ::std::ops::BitOrAssign for MyBitmask {
    #[inline]
    fn bitor_assign(&mut self, rhs: Self) {
        self.0 |= rhs.0;
    }
}

impl ::std::ops::BitXor for MyBitmask {
    type Output = Self;

    #[inline]
    fn bitxor(self, rhs: Self) -> Self::Output {
        Self(self.0 ^ rhs.0)
    }
}

impl ::std::ops::BitXorAssign for MyBitmask {
    #[inline]
    fn bitxor_assign(&mut self, rhs: Self) {
        self.0 ^= rhs.0
    }
}

impl ::std::ops::BitAnd for MyBitmask {
    type Output = Self;

    #[inline]
    fn bitand(self, rhs: Self) -> Self::Output {
        Self(self.0 & rhs.0)
    }
}

impl ::std::ops::BitAndAssign for MyBitmask {
    #[inline]
    fn bitand_assign(&mut self, rhs: Self) {
        self.0 &= rhs.0
    }
}

impl ::std::ops::Not for MyBitmask {
    type Output = Self;

    #[inline]
    fn not(self) -> Self::Output {
        Self(!self.0)
    }
}
}