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

Bit Sets — Dense Membership

A bit set represents membership with one bit per possible integer value.

When to use it

Use a bit set when the universe is bounded and densely numbered: permissions, CPU sets, feature flags, graph visitation, instrument IDs, or compact set algebra. Use HashSet for sparse or non-integer keys.

1. Representation

One u64 stores membership for 64 values:

word = value / 64
bit  = value % 64
mask = 1 << bit

2. Small implementation

#[derive(Clone, Debug, PartialEq)]
struct BitSet {
    words: Vec<u64>,
}

impl BitSet {
    fn with_capacity(bits: usize) -> Self {
        Self { words: vec![0; bits.div_ceil(64)] }
    }

    fn insert(&mut self, value: usize) {
        let (word, bit) = (value / 64, value % 64);
        self.words[word] |= 1_u64 << bit;
    }

    fn remove(&mut self, value: usize) {
        let (word, bit) = (value / 64, value % 64);
        self.words[word] &= !(1_u64 << bit);
    }

    fn contains(&self, value: usize) -> bool {
        let (word, bit) = (value / 64, value % 64);
        self.words.get(word).is_some_and(|bits| bits & (1_u64 << bit) != 0)
    }

    fn intersection(&self, other: &Self) -> Self {
        Self {
            words: self.words.iter().zip(&other.words)
                .map(|(left, right)| left & right).collect(),
        }
    }

    fn len(&self) -> u32 {
        self.words.iter().map(|word| word.count_ones()).sum()
    }
}

fn main() {
    let mut left = BitSet::with_capacity(128);
    left.insert(3);
    left.insert(70);

    let mut right = BitSet::with_capacity(128);
    right.insert(3);
    right.insert(90);

    assert!(left.contains(70));
    assert_eq!(left.intersection(&right).len(), 1);
    left.remove(3);
    assert!(!left.contains(3));
}

Membership updates are O(1). Union and intersection process one machine word at a time in O(universe / word_size) and are often vectorizable.

3. Tradeoffs

A million possible IDs require about 125 KB regardless of how many are present. That is excellent when many IDs occur and wasteful when only a handful do. Bounds policy is part of the API: the example panics when inserting beyond its declared capacity but safely returns false for an out-of-range lookup.

4. What you should internalize

Bit sets are arrays of membership bits. They provide compact dense membership and extremely fast bulk set operations, but require a bounded integer domain.

Exercise

Add union, difference, and count operations to the example. Test values on both sides of a word boundary, such as 63, 64, and 65.