Zig: Pointer Stability for ArrayLists
Zig Devlog: Recent Updates to Main
This page serves as a curated chronicle of the latest modifications integrated into the Zig main branch.
🛡️ Pointer Stability for ArrayLists
Date: August 27, 2026 | Author: Robbie Lyman
Back in 2024, "locks" were introduced to the std Hash Map containers to ensure pointer stability. Thanks to a pull request initiated by Leo Emar-Kar in 2025, this memory safety mechanism has now been extended to std.ArrayList.
The Core Concept
To utilize this feature, you must manage the stability of your pointers manually:
- Call
lockPointers()the moment you store a pointer (or a slice) to an element within theArrayList. - Call
unlockPointers()once those references are no longer required.
The Problem: Memory Relocation
Consider a scenario where you manage two lists: one for raw input data (history) and one for specific segments of interest (lines).
const std = @import("std");
const Context = struct {
history: std.ArrayList([]const u8),
// ... other fields
fn parse(ctx: *Context, allocator: std.mem.Allocator, input: []const u8) !void {
// Logic to add input to history and store slices in lines
}
};
The danger here is that the elements in Context.lines.items are dependent on the memory address of Context.history.items. If history exceeds its current capacity, it will reallocate its backing memory to a new location.
Visualizing the Failure
Reproducing the Bug
In the following test, we store pointers after the first parse call, then add more data:
test "Context.parse" {
const input = "I m first!\n";
const input_two = "But this text is juuuuuuuuuuuuuuuuuuuuuuuuust long enough that it causes a problem!";
// ... setup and first parse ...
// The second parse triggers a resize of the history ArrayList
}
When running zig test, the result is catastrophic:
Expected:
I m first!Found:UUUUUUUUUU
1/1 blah.test.Context.parse...FAIL (TestExpectedEqual)
This is a classic memory bug. Depending on your allocator, this might be a silent failure or a crash, making it incredibly difficult to debug.
The Solution: Explicit Locking
By adding pointer locks, we can turn a silent memory corruption into an explicit panic:
try ctx.parse(gpa, input);
+ ctx.history.lockPointers();
+ defer ctx.history.unlockPointers();
try ctx.parse(gpa, input_two);
Now, instead of garbage data, Zig provides a stack trace:
panic: reached unreachable code.../lib/std/array_list.zig:1348:50: in ensureTotalCapacityPreciseself.pointer_stability.assertUnlocked();
This immediately flags that the failure is a memory safety violation rather than a logic error.
Important Nuance: Ordering
Unlike Hash Maps, ArrayList is an ordered sequence. This means elements can move even without a reallocation.
- Warning: Functions like
orderedRemove()orpop()shift elements. - Result: Even though these don't allocate memory, they will trigger the
lockPointers()assertion because they invalidate the stability of the pointers.
Summary of Stability Requirements:
- Prevent reallocation during growth.
- Prevent element shifting via removal.
- Ensure
unlockPointers()is called to allow future growth.
📦 Package Management Migration
Date: June 30, 2026 | Author: Andrew Kelley
The logic for package management has been officially migrated from the compiler itself to the build system (specifically the maker process). This separation aligns with the new architecture where build.zig scripts run in a distinct process.
Command Changes
The following subcommands are now handled by the maker process:
zig buildzig fetchzig initzig libc
What was moved?
The following components were stripped from the compiler executable and are now shipped as source code:
| Category | Components Moved |
|---|---|
| Networking | HTTP client, TLS (Transport Layer Security), Git protocol |
| Compression | xz, gzip, zstd, flate, zip |
| Configuration | Parsing and validation of build.zig.zon files |
| Security | Associated cryptography for networking |
Key Advantages
- Easier Iteration: Users and contributors can now patch package management logic without needing to recompile the entire compiler.
- Enhanced Safety: Because the
makerexecutable is compiled inReleaseSafemode, networking operations now benefit from active safety checks. - Hardware Optimization: Cryptography and file hashing can now utilize host-specific CPU instructions that are typically too niche to include in a general compiler distribution.
Motivation
The primary driver for this change was the need to expose a build server protocol, which is essential for unblocking the development of ZLS (Zig Language Server) following the separation of the maker and configurer processes.
Mathematically, the complexity of the compiler binary is reduced:
Conceptual representation of the separated Maker process.