Zig's ArrayList Now Locks Pointers to Prevent Memory Bugs
Zig: Pointer Stability for ArrayLists
Zig's standard library has added pointer stability locks to ArrayList, following the same technique used for Hash Maps since 2024. By calling lockPointers() after storing pointers to elements, developers can catch memory-safety violations at runtime with a clear panic instead of subtle corruption. The devlog explains the bug with a contrived example and notes that ordered operations like pop() and orderedRemove() also trigger the assertion.
The problem is that elements of Context.lines.items depend on the location of Context.history.items, but this location may change if Context.history needs to grow beyond its current capacity.
- amluto
This seems weak.
In a language like Rust, the compiler will “lock” the pointers for you, and you can’t forget.
In a language like C++ (and presumably Zig), one could, in theory at least, have the iterators and slices that reference the storage of a dynamic array hold some sort of lock that pins the storage.
But this API requires the programmer to remember to lock the pointers and also requires the programmer to keep the lock alive for the correct region of code. And it looks to me like even the example in the blog post has the lock taken completely outside the function that requires stability, so there is nothing whatsoever that gets the lock scoping right. Even the type system can’t help — the offending parse function can’t declare that it wants a pointer-locked ArrayList parameter.
- _bohm
It's a nice feature but I can't help feeling like, if you need a stable pointer to an item in a collection, ArrayList is the wrong data structure to use? Maybe someone can chime in and give me an example of when you'd do this instead of, e.g., just storing an index. Alternatively, you could use an Unrolled Linked List (FKA SegmentedList in Zig before it was removed in 0.16, not sure why).
- portly
This makes a lot of sense if you consider that it is consistent with the rest of the language. It is one more way to set up tripwires in your code to to catch your own programming errors. Similar to using asserts in your functions to vet input and output.
I use Array list a lot so excited to add this throughout the code to harden them.
I can imagine this is not everyone's cup of tea, but then you probably also wouldn't enjoy any of the other explicitness.
- Rendello
Aside: one Zig (syntax) feature that I really missed in Rust is shown in the second code block, namely prefixed multi-line string literals à la:
const text =
\\This is a long comment
\\But I can split it among lines arbitrarily
\\And keep my indentation.
;
I've started using the Rust macro library `docstr` [1], which does the same thing:
const TEXT: &'static str = docstr!(
/// Now I can do it in Rust, too.
/// I prefer this style a lot of the time
/// for long texts.
);
It even works with macros (example from the docs):
let greeting: String = docstr!(format!
/// Hello, my name is {name}.
/// I am {} years old!
age
);
- boricj
It took me a minute to understand that this asserts on pointer change within the container, rather than lock/unlock the data structure like a SDL surface.
I recently implemented a custom C++ container for a path whose components could be iterated, backed by a std::string. I just store indices and a reference to the string, such that my iterators are not invalidated if the std::string gets reallocated after being modified. Far less error prone for little added cost.