ArrayList
const std = @import("std");
const expect = std.testing.expect;
test "build up a list" {
const gpa = std.testing.allocator;
// `.empty` replaces `init(allocator)`. The list does not store the
// allocator, so every method that may allocate takes one.
var list: std.ArrayList(u8) = .empty;
defer list.deinit(gpa);
try list.append(gpa, 'h');
try list.appendSlice(gpa, "ello");
try expect(std.mem.eql(u8, list.items, "hello"));
try expect(list.items.len == 5);
}
test "items is a plain slice" {
const gpa = std.testing.allocator;
var list: std.ArrayList(u32) = .empty;
defer list.deinit(gpa);
for (0..5) |i| try list.append(gpa, @intCast(i * i));
// `.items` is a `[]T` into the list's buffer, valid until the next
// reallocation, so do not hold it across an append.
try expect(list.items[4] == 16);
}
test "pop and remove" {
const gpa = std.testing.allocator;
var list: std.ArrayList(u8) = .empty;
defer list.deinit(gpa);
try list.appendSlice(gpa, "abcd");
try expect(list.pop().? == 'd');
_ = list.orderedRemove(0); // shifts the rest down
try expect(std.mem.eql(u8, list.items, "bc"));
}
test "preallocate when the size is known" {
const gpa = std.testing.allocator;
var list: std.ArrayList(u8) = .empty;
defer list.deinit(gpa);
// One allocation instead of a growth sequence.
try list.ensureTotalCapacity(gpa, 100);
for (0..100) |_| list.appendAssumeCapacity('x');
try expect(list.items.len == 100);
}
test "take ownership of the buffer" {
const gpa = std.testing.allocator;
var list: std.ArrayList(u8) = .empty;
try list.appendSlice(gpa, "owned");
// `toOwnedSlice` hands you the memory and empties the list, so the
// caller frees the slice rather than deinit-ing the list.
const slice = try list.toOwnedSlice(gpa);
defer gpa.free(slice);
try expect(std.mem.eql(u8, slice, "owned"));
}It is unmanaged now
This is the change most likely to break code you find online. std.ArrayList
used to store its allocator:
var list = std.ArrayList(u8).init(allocator); // old
defer list.deinit();
try list.append('a');
On master, ArrayList is the unmanaged variant. It holds no allocator, so
you supply one to every method that might allocate:
var list: std.ArrayList(u8) = .empty; // current
defer list.deinit(gpa);
try list.append(gpa, 'a');
More typing, but the allocator is visible at every call site that can fail, and the struct is two words smaller.
.items is a borrowed slice
list.items points into the list’s buffer. Appending may reallocate, which
invalidates any slice you were holding. Take .items when you need it; do not
stash it across mutations.
Preallocate when you can
ensureTotalCapacity followed by appendAssumeCapacity turns a growth
sequence into one allocation, and the AssumeCapacity calls cannot fail, so
they need no try.
Handing off ownership
toOwnedSlice(gpa) gives you the buffer and leaves the list empty. The caller
now frees the slice, and the list needs no deinit.