Skip to main content
BitSet provides efficient storage and manipulation of sets of integers represented as bits.

Type Variants

Zig provides three BitSet types:
  • StaticBitSet - Fixed size known at compile time
  • DynamicBitSet - Runtime-sized with allocator
  • DynamicBitSetUnmanaged - Runtime-sized without storing allocator

StaticBitSet

pub fn StaticBitSet(comptime size: usize) type
Fixed-size bit set with compile-time known size.

Creation

const BitSet = std.bit_set.StaticBitSet(128);
var bits = BitSet.initEmpty();
var all_set = BitSet.initFull();

Methods

initEmpty

pub fn initEmpty() StaticBitSet(size)
Creates a bit set with all bits unset.

initFull

pub fn initFull() StaticBitSet(size)
Creates a bit set with all bits set.

set

pub fn set(self: *StaticBitSet(size), index: usize) void
Sets the bit at the given index.

unset

pub fn unset(self: *StaticBitSet(size), index: usize) void
Clears the bit at the given index.

toggle

pub fn toggle(self: *StaticBitSet(size), index: usize) void
Toggles the bit at the given index.

isSet

pub fn isSet(self: StaticBitSet(size), index: usize) bool
Returns whether the bit at the given index is set.

count

pub fn count(self: StaticBitSet(size)) usize
Returns the number of set bits (population count).

setAll

pub fn setAll(self: *StaticBitSet(size)) void
Sets all bits.

unsetAll

pub fn unsetAll(self: *StaticBitSet(size)) void
Clears all bits.

toggleAll

pub fn toggleAll(self: *StaticBitSet(size)) void
Toggles all bits.

Set Operations

setUnion

pub fn setUnion(self: StaticBitSet(size), other: StaticBitSet(size)) StaticBitSet(size)
Returns the union of two bit sets (OR operation).

setIntersection

pub fn setIntersection(self: StaticBitSet(size), other: StaticBitSet(size)) StaticBitSet(size)
Returns the intersection of two bit sets (AND operation).

setDifference

pub fn setDifference(self: StaticBitSet(size), other: StaticBitSet(size)) StaticBitSet(size)
Returns the difference (bits in self but not in other).

toggleSet

pub fn toggleSet(self: *StaticBitSet(size), other: StaticBitSet(size)) void
Toggles all bits that are set in other (XOR operation).

Iteration

iterator

pub fn iterator(self: *const StaticBitSet(size)) Iterator
Returns an iterator over set bit indices.
var it = bits.iterator(.{});
while (it.next()) |index| {
    std.debug.print("Bit {} is set\n", .{index});
}

Usage Example

const std = @import("std");
const StaticBitSet = std.bit_set.StaticBitSet;

pub fn main() void {
    const BitSet = StaticBitSet(64);
    
    var bits = BitSet.initEmpty();
    
    // Set some bits
    bits.set(5);
    bits.set(10);
    bits.set(42);
    
    std.debug.print("Count: {}\n", .{bits.count()}); // 3
    std.debug.print("Is 5 set? {}\n", .{bits.isSet(5)}); // true
    std.debug.print("Is 7 set? {}\n", .{bits.isSet(7)}); // false
    
    // Set operations
    var other = BitSet.initEmpty();
    other.set(5);
    other.set(15);
    
    const union_bits = bits.setUnion(other);
    std.debug.print("Union count: {}\n", .{union_bits.count()}); // 4
    
    const intersection = bits.setIntersection(other);
    std.debug.print("Intersection count: {}\n", .{intersection.count()}); // 1
    
    // Iterate
    var it = bits.iterator(.{});
    while (it.next()) |index| {
        std.debug.print("Set: {}\n", .{index});
    }
}

DynamicBitSet

pub const DynamicBitSet
Dynamically-sized bit set with an allocator.

Creation and Destruction

initEmpty

pub fn initEmpty(allocator: Allocator, bit_length: usize) !DynamicBitSet
Creates an empty bit set with the specified capacity.

initFull

pub fn initFull(allocator: Allocator, bit_length: usize) !DynamicBitSet
Creates a bit set with all bits set.

deinit

pub fn deinit(self: *DynamicBitSet) void
Frees the bit set’s memory.

Resizing

resize

pub fn resize(self: *DynamicBitSet, new_len: usize, value: bool) !void
Resizes the bit set. New bits are set to value.

Usage Example

const std = @import("std");
const DynamicBitSet = std.bit_set.DynamicBitSet;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    var bits = try DynamicBitSet.initEmpty(allocator, 100);
    defer bits.deinit();
    
    bits.set(50);
    bits.set(75);
    
    std.debug.print("Count: {}\n", .{bits.count()});
    
    // Resize
    try bits.resize(200, false);
    bits.set(150);
}

DynamicBitSetUnmanaged

pub const DynamicBitSetUnmanaged
Dynamically-sized bit set that doesn’t store the allocator.
All methods that allocate require passing the allocator explicitly.

Methods

Same as DynamicBitSet, but allocator is passed to each method:
pub fn initEmpty(allocator: Allocator, bit_length: usize) !DynamicBitSetUnmanaged
pub fn deinit(self: *DynamicBitSetUnmanaged, allocator: Allocator) void
pub fn resize(self: *DynamicBitSetUnmanaged, allocator: Allocator, new_len: usize, value: bool) !void

Use Cases

Tracking Visited Nodes

var visited = StaticBitSet(graph.node_count).initEmpty();
for (nodes_to_visit) |node_id| {
    if (!visited.isSet(node_id)) {
        visited.set(node_id);
        // Process node
    }
}

Flags and Permissions

const Permissions = StaticBitSet(8);
const READ = 0;
const WRITE = 1;
const EXECUTE = 2;

var perms = Permissions.initEmpty();
perms.set(READ);
perms.set(WRITE);

if (perms.isSet(WRITE)) {
    // Allow write
}

Bloom Filters

var bloom = try DynamicBitSet.initEmpty(allocator, 1000);
defer bloom.deinit();

const hash1 = hashFunction1(key) % 1000;
const hash2 = hashFunction2(key) % 1000;
bloom.set(hash1);
bloom.set(hash2);

Performance

  • Set/Unset/Toggle: O(1)
  • Count: O(n) where n is number of machine words
  • Set operations: O(n) where n is number of machine words
  • Memory: ⌈size/8⌉ bytes plus overhead
BitSet operations are highly optimized using CPU instructions like popcnt for counting set bits.
  • EnumSet - Type-safe bit set for enums (see std namespace)
  • HashMap - When you need to map keys to boolean values

Build docs developers (and LLMs) love