From 35c5611f0707a46f6314a7b4e4597ff56bfead18 Mon Sep 17 00:00:00 2001 From: Justus Klausecker Date: Wed, 11 Feb 2026 00:41:04 +0100 Subject: [PATCH 1/4] std.Deque: add `*Ptr` variants of getter functions This makes it practical to store large items or items that are meant to be mutable directly inside of the deque. It is the responsibility of the user to stop using the returned pointers after calling a function that could invalidate them. --- lib/std/deque.zig | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/std/deque.zig b/lib/std/deque.zig index 267b8a0afe..6874ebe394 100644 --- a/lib/std/deque.zig +++ b/lib/std/deque.zig @@ -180,12 +180,24 @@ pub fn Deque(comptime T: type) type { return deque.buffer[deque.head]; } + /// Return pointer to the first item in the deque or null if empty. + pub fn frontPtr(deque: *const Self) ?*T { + if (deque.len == 0) return null; + return &deque.buffer[deque.head]; + } + /// Return the last item in the deque or null if empty. pub fn back(deque: *const Self) ?T { if (deque.len == 0) return null; return deque.buffer[deque.bufferIndex(deque.len - 1)]; } + /// Return the last item in the deque or null if empty. + pub fn backPtr(deque: *const Self) ?*T { + if (deque.len == 0) return null; + return &deque.buffer[deque.bufferIndex(deque.len - 1)]; + } + /// Return the item at the given index in the deque. /// /// The first item in the queue is at index 0. @@ -196,6 +208,16 @@ pub fn Deque(comptime T: type) type { return deque.buffer[deque.bufferIndex(index)]; } + /// Return pointer to the item at the given index in the deque. + /// + /// The first item in the queue is at index 0. + /// + /// Asserts that the index is in-bounds. + pub fn atPtr(deque: *const Self, index: usize) *T { + assert(index < deque.len); + return &deque.buffer[deque.bufferIndex(index)]; + } + /// Remove and return the first item in the deque or null if empty. pub fn popFront(deque: *Self) ?T { if (deque.len == 0) return null; From 527e97b25201a1a252bcff6d79198513babda2db Mon Sep 17 00:00:00 2001 From: Justus Klausecker Date: Wed, 11 Feb 2026 00:57:19 +0100 Subject: [PATCH 2/4] std.Deque: add `*Slice` variants of push functions This mirrors the `*Slice` variants e.g. `std.ArrayList` already provides. --- lib/std/deque.zig | 143 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/lib/std/deque.zig b/lib/std/deque.zig index 6874ebe394..236d476135 100644 --- a/lib/std/deque.zig +++ b/lib/std/deque.zig @@ -174,6 +174,91 @@ pub fn Deque(comptime T: type) type { deque.len += 1; } + /// Add `items` to the front of the deque. + /// This is equivalent to iterating `items` in reverse and calling + /// `pushFront` on every single entry. + /// + /// Invalidates element pointers if additional memory is needed. + pub fn pushFrontSlice(deque: *Self, gpa: Allocator, items: []const T) error{OutOfMemory}!void { + try deque.ensureUnusedCapacity(gpa, items.len); + return deque.pushFrontSliceAssumeCapacity(items); + } + + /// Add `items` to the front of the deque. + /// This is equivalent to iterating `items` in reverse and calling + /// `pushFront` on every single entry. + /// + /// Never invalidates element pointers. + /// + /// If the deque lacks unused capacity for the additional items, returns + /// `error.OutOfMemory`. + pub fn pushFrontSliceBounded(deque: *Self, items: []const T) error{OutOfMemory}!void { + if (deque.buffer.len - deque.len < items.len) return error.OutOfMemory; + return deque.pushFrontSliceAssumeCapacity(items); + } + + /// Add `items` to the front of the deque. + /// This is equivalent to iterating `items` in reverse and calling + /// `pushFront` on every single entry. + /// + /// Never invalidates element pointers. + /// + /// Asserts that the deque can hold the additional items. + pub fn pushFrontSliceAssumeCapacity(deque: *Self, items: []const T) void { + assert(deque.buffer.len - deque.len >= items.len); + if (deque.head < items.len) { + @memcpy(deque.buffer[0..deque.head], items[items.len - deque.head ..]); + deque.head = deque.buffer.len - items.len + deque.head; + @memcpy(deque.buffer[deque.head..], items.ptr); + } else { + deque.head -= items.len; + @memcpy(deque.buffer[deque.head..][0..items.len], items); + } + deque.len += items.len; + } + + /// Add `items` to the back of the deque. + /// This is equivalent to iterating `items` in order and calling + /// `pushBack` on every single entry. + /// + /// Invalidates element pointers if additional memory is needed. + pub fn pushBackSlice(deque: *Self, gpa: Allocator, items: []const T) error{OutOfMemory}!void { + try deque.ensureUnusedCapacity(gpa, items.len); + return deque.pushBackSliceAssumeCapacity(items); + } + + /// Add `items` to the back of the deque. + /// This is equivalent to iterating `items` in order and calling + /// `pushBack` on every single entry. + /// + /// Never invalidates element pointers. + /// + /// If the deque lacks unused capacity for the additional items, returns + /// `error.OutOfMemory`. + pub fn pushBackSliceBounded(deque: *Self, items: []const T) error{OutOfMemory}!void { + if (deque.buffer.len - deque.len < items.len) return error.OutOfMemory; + return deque.pushBackSliceAssumeCapacity(items); + } + + /// Add `items` to the back of the deque. + /// This is equivalent to iterating `items` in order and calling + /// `pushBack` on every single entry. + /// + /// Never invalidates element pointers. + /// + /// Asserts that the deque can hold the additional items. + pub fn pushBackSliceAssumeCapacity(deque: *Self, items: []const T) void { + assert(deque.buffer.len - deque.len >= items.len); + const trailing_buffer = deque.buffer[deque.bufferIndex(deque.len)..]; + if (trailing_buffer.len < items.len) { + @memcpy(trailing_buffer, items[0..trailing_buffer.len]); + @memcpy(deque.buffer.ptr, items[trailing_buffer.len..]); + } else { + @memcpy(trailing_buffer[0..items.len], items); + } + deque.len += items.len; + } + /// Return the first item in the deque or null if empty. pub fn front(deque: *const Self) ?T { if (deque.len == 0) return null; @@ -350,6 +435,40 @@ test "slow growth" { try testing.expectEqual(null, q.popBack()); } +test "slice" { + const testing = std.testing; + const gpa = testing.allocator; + + var q: Deque(i32) = .empty; + defer q.deinit(gpa); + + try q.pushBackSlice(gpa, &.{ 3, 4, 5 }); + try q.pushBackSlice(gpa, &.{ 6, 7 }); + try q.pushFrontSlice(gpa, &.{2}); + try q.pushBackSlice(gpa, &.{}); + try q.pushFrontSlice(gpa, &.{ 0, 1 }); + try q.pushFrontSlice(gpa, &.{}); + + try testing.expectEqual(0, q.popFront()); + try testing.expectEqual(1, q.popFront()); + try testing.expectEqual(7, q.popBack()); + try testing.expectEqual(6, q.popBack()); + + try q.pushFrontSlice(gpa, &.{ 0, 1 }); + try q.pushBackSlice(gpa, &.{ 6, 7 }); + + try testing.expectEqual(0, q.popFront()); + try testing.expectEqual(1, q.popFront()); + try testing.expectEqual(2, q.popFront()); + try testing.expectEqual(7, q.popBack()); + try testing.expectEqual(6, q.popBack()); + try testing.expectEqual(3, q.popFront()); + try testing.expectEqual(4, q.popFront()); + try testing.expectEqual(5, q.popBack()); + try testing.expectEqual(null, q.popFront()); + try testing.expectEqual(null, q.popBack()); +} + test "fuzz against ArrayList oracle" { try std.testing.fuzz({}, fuzzAgainstArrayList, .{}); } @@ -384,6 +503,8 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void { const Action = enum { push_back, push_front, + push_back_slice, + push_front_slice, pop_back, pop_front, grow, @@ -406,6 +527,28 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void { q.pushFrontBounded(item), ); }, + .push_back_slice => { + var buffer: [std.math.maxInt(u3)]u32 = undefined; + const items = buffer[0..random.int(u3)]; + for (items) |*item| { + item.* = random.int(u8); + } + try testing.expectEqual( + l.appendSliceBounded(items), + q.pushBackSliceBounded(items), + ); + }, + .push_front_slice => { + var buffer: [std.math.maxInt(u3)]u32 = undefined; + const items = buffer[0..random.int(u3)]; + for (items) |*item| { + item.* = random.int(u8); + } + try testing.expectEqual( + l.insertSliceBounded(0, items), + q.pushFrontSliceBounded(items), + ); + }, .pop_back => { try testing.expectEqual(l.pop(), q.popBack()); }, From 623723507f7f307ac888ce483b422bde7da9984b Mon Sep 17 00:00:00 2001 From: Justus Klausecker Date: Wed, 11 Feb 2026 08:29:30 +0100 Subject: [PATCH 3/4] std.Deque: add `peek` and `*Ptr` functions to `Iterator` The iterator should be as powerful as manual access via `as` and `asPtr` to justify its existence. --- lib/std/deque.zig | 57 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/lib/std/deque.zig b/lib/std/deque.zig index 236d476135..6a2cbdaeb4 100644 --- a/lib/std/deque.zig +++ b/lib/std/deque.zig @@ -323,13 +323,24 @@ pub fn Deque(comptime T: type) type { deque: *const Self, index: usize, + pub fn peek(it: Iterator) ?T { + if (it.index >= it.deque.len) return null; + return it.deque.at(it.index); + } pub fn next(it: *Iterator) ?T { - if (it.index < it.deque.len) { - defer it.index += 1; - return it.deque.at(it.index); - } else { - return null; - } + const item = it.peek() orelse return null; + it.index += 1; + return item; + } + + pub fn peekPtr(it: Iterator) ?*T { + if (it.index >= it.deque.len) return null; + return it.deque.atPtr(it.index); + } + pub fn nextPtr(it: *Iterator) ?*T { + const item_ptr = it.peekPtr() orelse return null; + it.index += 1; + return item_ptr; } }; @@ -469,6 +480,40 @@ test "slice" { try testing.expectEqual(null, q.popBack()); } +test "iterator" { + const testing = std.testing; + const gpa = testing.allocator; + + var q: Deque(i32) = .empty; + defer q.deinit(gpa); + + const items: []const i32 = &.{ 0, 1, 2, 3, 4, 5 }; + try q.pushFrontSlice(gpa, items); + + { + var it = q.iterator(); + for (items) |item| { + try testing.expectEqual(item, it.peek()); + try testing.expectEqual(item, it.next()); + } + try testing.expectEqual(null, it.peek()); + try testing.expectEqual(null, it.next()); + } + { + var it = q.iterator(); + for (items) |item| { + if (it.peekPtr()) |ptr| { + try testing.expectEqual(item, ptr.*); + } else return error.TextExpectedNonNull; + if (it.nextPtr()) |ptr| { + try testing.expectEqual(item, ptr.*); + } else return error.TextExpectedNonNull; + } + try testing.expectEqual(null, it.peekPtr()); + try testing.expectEqual(null, it.nextPtr()); + } +} + test "fuzz against ArrayList oracle" { try std.testing.fuzz({}, fuzzAgainstArrayList, .{}); } From 13e42b16cd91a92f18e9666e0ad8617b396c215a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 12 Feb 2026 21:45:52 -0800 Subject: [PATCH 4/4] std.deque: fix typo in unit test --- lib/std/deque.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/deque.zig b/lib/std/deque.zig index 6a2cbdaeb4..2d4cfc8600 100644 --- a/lib/std/deque.zig +++ b/lib/std/deque.zig @@ -504,10 +504,10 @@ test "iterator" { for (items) |item| { if (it.peekPtr()) |ptr| { try testing.expectEqual(item, ptr.*); - } else return error.TextExpectedNonNull; + } else return error.TestExpectedNonNull; if (it.nextPtr()) |ptr| { try testing.expectEqual(item, ptr.*); - } else return error.TextExpectedNonNull; + } else return error.TestExpectedNonNull; } try testing.expectEqual(null, it.peekPtr()); try testing.expectEqual(null, it.nextPtr());