Gleam Bit Arrays
Bit arrays store raw binary data — sequences of bits and bytes. They are essential for working with network protocols, file formats, cryptographic hashes, image data, and anything involving bytes rather than text. Gleam inherits Erlang's powerful bit syntax, making binary operations both expressive and safe.
What Is a Bit Array?
Bit Array vs String
──────────────────────────────────────────────────
String: "hello" → UTF-8 encoded text
human-readable
use for names, messages, labels
BitArray: <<104, 101, 108, 108, 111>>
raw bytes
use for files, protocols, hashes
"hello" and <<104,101,108,108,111>> contain the same
bytes, but one is text and one is binary data.
Creating Bit Arrays
// Byte literals
let bytes = <<72, 101, 108, 108, 111>> // "Hello" as bytes
// From a string (UTF-8 encoding)
let from_string = <<"Hello":utf8>>
// Single bits and values
let flags = <<1:1, 0:1, 1:1>> // 3 bits: 1, 0, 1
let port = <<8080:16>> // 16-bit integer
let uuid = <<0:128>> // 128 zero bits
Bit Array Literal Syntax
──────────────────────────────────────────────────
<> → 8-bit integer (default)
<> → value using exactly `size` bits
<> → value as UTF-8 encoded bytes
<> → multiple segments joined
<<72, 101, 108>>
72 = 0x48 = 'H'
101 = 0x65 = 'e'
108 = 0x6C = 'l'
Bit Array Segments
Each segment inside << >> can specify size, type, and endianness:
Segment Options
──────────────────────────────────────────────────
<> → N bits wide
<> → 8 bits (1 byte)
<> → 16 bits (2 bytes)
<> → 32 bits (4 bytes)
<> → 64 bits (8 bytes)
<> → little-endian byte order
<> → big-endian byte order (default)
<> → system byte order
<> → IEEE 754 float
<> → UTF-8 string
<> → UTF-16 string
<> → any number of bits
Pattern Matching on Bit Arrays
The most powerful feature of Erlang's bit syntax — and Gleam inherits it — is pattern matching on binary structures:
// Parse a simple 4-byte IPv4 address
pub fn parse_ipv4(data: BitArray) -> Result(#(Int,Int,Int,Int), Nil) {
case data {
<> -> Ok(#(a, b, c, d))
_ -> Error(Nil)
}
}
// parse_ipv4(<<192, 168, 1, 1>>) → Ok(#(192, 168, 1, 1))
IPv4 Pattern Match
──────────────────────────────────────────────────
data = <<192, 168, 1, 1>>
<>
a = 192 (first 8 bits)
b = 168 (next 8 bits)
c = 1 (next 8 bits)
d = 1 (last 8 bits)
Result: #(192, 168, 1, 1)
Reading a Binary Protocol Header
// A simple message format:
// [version: 8 bits][type: 8 bits][length: 16 bits][payload: length bytes]
type MessageHeader {
MessageHeader(version: Int, msg_type: Int, length: Int)
}
pub fn parse_header(data: BitArray) -> Result(#(MessageHeader, BitArray), Nil) {
case data {
<> ->
Ok(#(MessageHeader(version, msg_type, length), rest))
_ -> Error(Nil)
}
}
Protocol Header Layout
──────────────────────────────────────────────────
Byte: 0 1 2-3 4+
┌────────┬────────┬──────────┬──────────────┐
│version │ type │ length │ payload │
│ 8 bits │ 8 bits │ 16 bits │ length bytes │
└────────┴────────┴──────────┴──────────────┘
Building Bit Arrays
// Build a 4-byte header
pub fn build_header(version: Int, msg_type: Int, payload: BitArray) -> BitArray {
let length = bit_array.byte_size(payload)
<>
}
let header = build_header(1, 42, <<"Hello":utf8>>)
// <<1, 42, 0, 5, 72, 101, 108, 108, 111>>
Common Bit Array Functions
import gleam/bit_array
bit_array.byte_size(ba) // number of bytes
bit_array.bit_size(ba) // number of bits
bit_array.to_string(ba) // convert to String (Result)
bit_array.from_string(s) // convert from String
bit_array.concat([a, b, c]) // join multiple bit arrays
bit_array.slice(ba, from, size) // extract a portion
bit_array.inspect(ba) // hex debug string
Practical Example — Simple Checksum
import gleam/bit_array
import gleam/list
pub fn checksum(data: BitArray) -> Int {
data
|> bit_array.to_list // convert to List(Int) of bytes
|> list.fold(0, fn(acc, byte) { acc + byte })
|> fn(sum) { sum % 256 } // 8-bit checksum
}
pub fn verify(data: BitArray, expected: Int) -> Bool {
checksum(data) == expected
}
pub fn main() {
let payload = <<"Hello":utf8>>
let cs = checksum(payload)
io.debug(cs) // some checksum value
io.debug(verify(payload, cs)) // True
io.debug(verify(<<"World":utf8>>, cs)) // False
}
Bit Arrays and Strings
Converting Between BitArray and String
──────────────────────────────────────────────────
String → BitArray:
bit_array.from_string("hello")
→ <<104, 101, 108, 108, 111>>
BitArray → String (may fail if not valid UTF-8):
bit_array.to_string(<<104, 101, 108, 108, 111>>)
→ Ok("hello")
bit_array.to_string(<<0xFF, 0xFE>>)
→ Error(Nil) (not valid UTF-8)
Key Points
Bit Array Essentials
──────────────────────────────────────────────────
1. BitArray stores raw binary data (bytes and bits)
2. Create with << >> literal syntax
3. Segment options: size, type (utf8, float), endianness
4. Pattern match on binary structures with case
5. Use for protocols, file formats, hashes, network data
6. gleam/bit_array module for utility functions
7. Convert to/from String with from_string / to_string
Bit arrays make Gleam a first-class language for systems programming and network applications. The ability to pattern match directly on binary structures — parsing a TCP packet, reading a file header, or decoding a binary protocol — without writing a single byte-shifting loop is one of the most powerful features Gleam inherits from Erlang.
