authorgravatar for andrew.kraevskii@gmail.comAndrew Kraevskiii <andrew.kraevskii@gmail.com> 2026-03-12 20:50:27+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-12 20:50:27+01:00
log47597a6d7cb7bbea313c15ba4c9f5dcecb972e64
tree95678afa1f15e027bfc8a581d6923bf4465039af
parentd5bfa657c48e9d023bb789fbf8dacbcbd42f528d

std.ArrayList: add toOwnedSliceAssert, shrinkToLen. (#30769)

Adds a set of new APIs to ArrayList with goal of making it easier to avoid toOwnedSlice foot gun: ```zig fn wrongUsageOfCurrentApi(gpa: std.mem.Allocator) ![2][]const u8 { var a: std.ArrayList(u8) = .empty; defer a.deinit(gpa); var b: std.ArrayList(u8) = .empty; defer b.deinit(gpa); // ... return .{ try a.toOwnedSlice(gpa), try b.toOwnedSlice(gpa), // oom here causes leak :( }; } fn correctUsageOfCurrentApi(gpa: std.mem.Allocator) ![2][]const u8 { var a: std.ArrayList(u8) = .empty; defer a.deinit(gpa); var b: std.ArrayList(u8) = .empty; defer b.deinit(gpa); // ... const a_slice = try a.toOwnedSlice(gpa); errdefer gpa.free(a_slice); const b_slice = try b.toOwnedSlice(gpa); // may be omited but good if error appear bellow later in development. errdefer gpa.free(b_slice); return .{ a_slice, b_slice, }; } fn proposedApi(gpa: std.mem.Allocator) ![2][]const u8 { var a: std.ArrayList(u8) = .empty; defer a.deinit(gpa); var b: std.ArrayList(u8) = .empty; defer b.deinit(gpa); // ... try a.shrinkToLen(gpa); try b.shrinkToLen(gpa); return .{ a.toOwnedSliceAssert(), b.toOwnedSliceAssert(), }; } ``` Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30769 Reviewed-by: Andrew Kelley <andrew@ziglang.org> Co-authored-by: andrew.kraevskiii <andrew.kraevskii@gmail.com> Co-committed-by: andrew.kraevskiii <andrew.kraevskii@gmail.com>

1 files changed, 124 insertions(+), 7 deletions(-)

lib/std/array_list.zig+124-7
......@@ -674,6 +674,26 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type {
674674 return result[0 .. result.len - 1 :sentinel];
675675 }
676676
677 /// The caller owns the returned memory. Empties this ArrayList.
678 /// Its capacity is cleared, making deinit() safe but unnecessary to call.
679 ///
680 /// Asserts what the capacity is equal to the length.
681 pub fn toOwnedSliceAssert(self: *Self) Slice {
682 assert(self.items.len == self.capacity);
683 const items = self.items;
684 self.* = .empty;
685 return items;
686 }
687
688 /// The caller owns the returned memory. ArrayList becomes empty.
689 /// Asserts what the capacity is equal to the length + 1.
690 pub fn toOwnedSliceSentinelAssert(self: *Self, comptime sentinel: T) SentinelSlice(sentinel) {
691 std.debug.assert(self.items.len + 1 == self.capacity);
692 self.appendAssumeCapacity(sentinel);
693 const result = self.toOwnedSliceAssert();
694 return result[0 .. result.len - 1 :sentinel];
695 }
696
677697 /// Creates a copy of this ArrayList.
678698 pub fn clone(self: Self, gpa: Allocator) Allocator.Error!Self {
679699 var cloned = try Self.initCapacity(gpa, self.capacity);
......@@ -1106,6 +1126,20 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type {
11061126 /// May invalidate element pointers.
11071127 /// Asserts that the new length is less than or equal to the previous length.
11081128 pub fn shrinkAndFree(self: *Self, gpa: Allocator, new_len: usize) void {
1129 self.shrinkAndFreePrecise(gpa, new_len) catch |e| switch (e) {
1130 error.OutOfMemory => {
1131 // No problem, capacity is still correct then.
1132 self.items.len = new_len;
1133 return;
1134 },
1135 };
1136 }
1137
1138 /// Reduce allocated capacity to `new_len`.
1139 /// May invalidate element pointers.
1140 /// Asserts that the new length is less than or equal to the previous length.
1141 /// If succeds capacity is guaranteed to be equal to the length.
1142 pub fn shrinkAndFreePrecise(self: *Self, gpa: Allocator, new_len: usize) Allocator.Error!void {
11091143 assert(new_len <= self.items.len);
11101144
11111145 if (@sizeOf(T) == 0) {
......@@ -1120,13 +1154,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type {
11201154 return;
11211155 }
11221156
1123 const new_memory = gpa.alignedAlloc(T, alignment, new_len) catch |e| switch (e) {
1124 error.OutOfMemory => {
1125 // No problem, capacity is still correct then.
1126 self.items.len = new_len;
1127 return;
1128 },
1129 };
1157 const new_memory = try gpa.alignedAlloc(T, alignment, new_len);
11301158
11311159 @memcpy(new_memory, self.items[0..new_len]);
11321160 gpa.free(old_memory);
......@@ -1134,6 +1162,32 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type {
11341162 self.capacity = new_memory.len;
11351163 }
11361164
1165 /// Shrinks capacity to match length.
1166 /// May invalidate element pointers.
1167 /// If succeds it is safe to call toOwnedSliceAssert().
1168 pub fn shrinkToLen(self: *Self, gpa: Allocator) Allocator.Error!void {
1169 try self.shrinkAndFreePrecise(gpa, self.items.len);
1170 }
1171
1172 /// Shrinks or expands capacity to match length + 1.
1173 /// May invalidate element pointers.
1174 /// If succeds it is safe to call toOwnedSliceSentinelAssert().
1175 pub fn shrinkToLenSentinel(self: *Self, gpa: Allocator) Allocator.Error!void {
1176 std.debug.assert(self.items.len <= self.capacity);
1177 const required_len = self.items.len + 1;
1178 switch (std.math.order(required_len, self.capacity)) {
1179 .eq => return,
1180 .gt => {
1181 try self.ensureTotalCapacityPrecise(gpa, required_len);
1182 },
1183 .lt => {
1184 self.items.len += 1;
1185 defer self.items.len -= 1;
1186 try self.shrinkToLen(gpa);
1187 },
1188 }
1189 }
1190
11371191 /// Reduce length to `new_len`.
11381192 /// Invalidates pointers to elements `items[new_len..]`.
11391193 /// Keeps capacity the same.
......@@ -2089,6 +2143,30 @@ test "shrinkAndFree with a copy" {
20892143 try testing.expect(mem.eql(i32, list.items, &.{ 3, 3, 3, 3 }));
20902144}
20912145
2146test "shrinkAndFreePrecise without resize succeeds" {
2147 var failing_allocator = testing.FailingAllocator.init(testing.allocator, .{ .resize_fail_index = 0 });
2148 const a = failing_allocator.allocator();
2149
2150 var list: Aligned(i32, null) = .empty;
2151 defer list.deinit(a);
2152
2153 try list.appendNTimes(a, 3, 16);
2154 try list.shrinkAndFreePrecise(a, 4);
2155 try testing.expectEqualSlices(i32, &.{ 3, 3, 3, 3 }, list.items);
2156 try testing.expectEqual(list.items.len, list.capacity);
2157}
2158
2159test "shrinkAndFreePrecise without resize and no copy failes" {
2160 var failing_allocator = testing.FailingAllocator.init(testing.allocator, .{ .resize_fail_index = 0, .fail_index = 1 });
2161 const a = failing_allocator.allocator();
2162
2163 var list: Aligned(i32, null) = .empty;
2164 defer list.deinit(a);
2165
2166 try list.appendNTimes(a, 3, 16);
2167 try std.testing.expectError(error.OutOfMemory, list.shrinkAndFreePrecise(a, 4));
2168}
2169
20922170test "addManyAsArray" {
20932171 const a = std.testing.allocator;
20942172 {
......@@ -2219,6 +2297,45 @@ test "toOwnedSliceSentinel" {
22192297 }
22202298}
22212299
2300test "toOwnedSliceAssert" {
2301 var failing_allocator: testing.FailingAllocator = .init(testing.allocator, .{
2302 .fail_index = 2,
2303 });
2304 const a = failing_allocator.allocator();
2305
2306 var list: Aligned(u8, null) = try .initCapacity(a, 6); // first alloc
2307 list.appendSliceAssumeCapacity(&.{ 1, 2, 3 });
2308
2309 try list.shrinkToLen(a); // first resize
2310 try std.testing.expectEqual(list.items.len, list.capacity);
2311 try list.shrinkToLen(a); // no alloc or resize
2312
2313 const slice = list.toOwnedSliceAssert();
2314 defer a.free(slice);
2315
2316 try std.testing.expectEqual(Aligned(u8, null).empty, list);
2317 try std.testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, slice);
2318}
2319
2320test "toOwnedSliceSentinelAssert" {
2321 const a = testing.allocator;
2322
2323 var list: Aligned(u8, null) = try .initCapacity(a, 6);
2324 list.appendSliceAssumeCapacity(&.{ 1, 2, 3 });
2325
2326 // shrinkToLenSentinel shrinks array
2327 try list.shrinkToLenSentinel(a);
2328
2329 // shrinkToLenSentinel expands array
2330 try list.shrinkToLen(a);
2331 try list.shrinkToLenSentinel(a);
2332
2333 const slice = list.toOwnedSliceSentinelAssert(10);
2334 defer a.free(slice);
2335
2336 try std.testing.expectEqualSentinel(u8, 10, &.{ 1, 2, 3 }, slice);
2337}
2338
22222339test "accepts unaligned slices" {
22232340 const a = testing.allocator;
22242341 {