authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2021-08-24 13:59:28+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-08-24 13:59:28+02:00
log8a37fe21764659c5f742454a4eb9950b5c747a42
tree1f2b5b9fcf5cd9aa4156e9c75cccc4590837f616
parent8c41a8e761cb609d927b0f2c3c3c094970d2eb58
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

BoundedArray: a simple way to represent small data whose max size is known (#9134)

This is a simple structure containing an array and a length, that can be viewed as a slice. It is useful to pass-by-copy small data whose exact size is known at runtime, but whose maximum size is known at comptime. This greatly simplifies code that otherwise would require an allocator, or reimplementing what this type does.

2 files changed, 322 insertions(+), 0 deletions(-)

lib/std/bounded_array.zig created+321
......@@ -0,0 +1,321 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("std.zig");
8const assert = std.debug.assert;
9const mem = std.mem;
10const testing = std.testing;
11
12/// A structure with an array and a length, that can be used as a slice.
13///
14/// Useful to pass around small arrays whose exact size is only known at
15/// runtime, but whose maximum size is known at comptime, without requiring
16/// an `Allocator`.
17///
18/// ```zig
19/// var actual_size = 32;
20/// var a = try BoundedArray(u8, 64).init(actual_size);
21/// var slice = a.slice(); // a slice of the 64-byte array
22/// var a_clone = a; // creates a copy - the structure doesn't use any internal pointers
23/// ```
24pub fn BoundedArray(comptime T: type, comptime capacity: usize) type {
25 return struct {
26 const Self = @This();
27 buffer: [capacity]T,
28 len: usize = 0,
29
30 /// Set the actual length of the slice.
31 /// Returns error.Overflow if it exceeds the length of the backing array.
32 pub fn init(len: usize) !Self {
33 if (len > capacity) return error.Overflow;
34 return Self{ .buffer = undefined, .len = len };
35 }
36
37 /// View the internal array as a mutable slice whose size was previously set.
38 pub fn slice(self: *Self) []T {
39 return self.buffer[0..self.len];
40 }
41
42 /// View the internal array as a constant slice whose size was previously set.
43 pub fn constSlice(self: Self) []const T {
44 return self.buffer[0..self.len];
45 }
46
47 /// Adjust the slice's length to `len`.
48 /// Does not initialize added items if any.
49 pub fn resize(self: *Self, len: usize) !void {
50 if (len > capacity) return error.Overflow;
51 self.len = len;
52 }
53
54 /// Copy the content of an existing slice.
55 pub fn fromSlice(m: []const T) !Self {
56 var list = try init(m.len);
57 std.mem.copy(T, list.slice(), m);
58 return list;
59 }
60
61 /// Return the element at index `i` of the slice.
62 pub fn get(self: Self, i: usize) T {
63 return self.constSlice()[i];
64 }
65
66 /// Set the value of the element at index `i` of the slice.
67 pub fn set(self: *Self, i: usize, item: T) void {
68 self.slice()[i] = item;
69 }
70
71 /// Return the maximum length of a slice.
72 pub fn capacity(self: Self) usize {
73 return self.buffer.len;
74 }
75
76 /// Check that the slice can hold at least `additional_count` items.
77 pub fn ensureUnusedCapacity(self: Self, additional_count: usize) !void {
78 if (self.len + additional_count > capacity) {
79 return error.Overflow;
80 }
81 }
82
83 /// Increase length by 1, returning a pointer to the new item.
84 pub fn addOne(self: *Self) !*T {
85 try self.ensureUnusedCapacity(1);
86 return self.addOneAssumeCapacity();
87 }
88
89 /// Increase length by 1, returning pointer to the new item.
90 /// Asserts that there is space for the new item.
91 pub fn addOneAssumeCapacity(self: *Self) *T {
92 assert(self.len < capacity);
93 self.len += 1;
94 return &self.slice()[self.len - 1];
95 }
96
97 /// Resize the slice, adding `n` new elements, which have `undefined` values.
98 /// The return value is a slice pointing to the uninitialized elements.
99 pub fn addManyAsArray(self: *Self, comptime n: usize) !*[n]T {
100 const prev_len = self.len;
101 try self.resize(self.len + n);
102 return self.slice()[prev_len..][0..n];
103 }
104
105 /// Remove and return the last element from the slice.
106 /// Asserts the slice has at least one item.
107 pub fn pop(self: *Self) T {
108 const item = self.get(self.len - 1);
109 self.len -= 1;
110 return item;
111 }
112
113 /// Remove and return the last element from the slice, or
114 /// return `null` if the slice is empty.
115 pub fn popOrNull(self: *Self) ?T {
116 return if (self.len == 0) null else self.pop();
117 }
118
119 /// Return a slice of only the extra capacity after items.
120 /// This can be useful for writing directly into it.
121 /// Note that such an operation must be followed up with a
122 /// call to `resize()`
123 pub fn unusedCapacitySlice(self: *Self) []T {
124 return self.buffer[self.len..];
125 }
126
127 /// Insert `item` at index `i` by moving `slice[n .. slice.len]` to make room.
128 /// This operation is O(N).
129 pub fn insert(self: *Self, i: usize, item: T) !void {
130 if (i >= self.len) {
131 return error.IndexOutOfBounds;
132 }
133 _ = try self.addOne();
134 var s = self.slice();
135 mem.copyBackwards(T, s[i + 1 .. s.len], s[i .. s.len - 1]);
136 self.buffer[i] = item;
137 }
138
139 /// Insert slice `items` at index `i` by moving `slice[i .. slice.len]` to make room.
140 /// This operation is O(N).
141 pub fn insertSlice(self: *Self, i: usize, items: []const T) !void {
142 try self.ensureUnusedCapacity(items.len);
143 self.len += items.len;
144 mem.copyBackwards(T, self.slice()[i + items.len .. self.len], self.constSlice()[i .. self.len - items.len]);
145 mem.copy(T, self.slice()[i .. i + items.len], items);
146 }
147
148 /// Replace range of elements `slice[start..start+len]` with `new_items`.
149 /// Grows slice if `len < new_items.len`.
150 /// Shrinks slice if `len > new_items.len`.
151 pub fn replaceRange(self: *Self, start: usize, len: usize, new_items: []const T) !void {
152 const after_range = start + len;
153 var range = self.slice()[start..after_range];
154
155 if (range.len == new_items.len) {
156 mem.copy(T, range, new_items);
157 } else if (range.len < new_items.len) {
158 const first = new_items[0..range.len];
159 const rest = new_items[range.len..];
160 mem.copy(T, range, first);
161 try self.insertSlice(after_range, rest);
162 } else {
163 mem.copy(T, range, new_items);
164 const after_subrange = start + new_items.len;
165 for (self.constSlice()[after_range..]) |item, i| {
166 self.slice()[after_subrange..][i] = item;
167 }
168 self.len -= len - new_items.len;
169 }
170 }
171
172 /// Extend the slice by 1 element.
173 pub fn append(self: *Self, item: T) !void {
174 const new_item_ptr = try self.addOne();
175 new_item_ptr.* = item;
176 }
177
178 /// Remove the element at index `i`, shift elements after index
179 /// `i` forward, and return the removed element.
180 /// Asserts the slice has at least one item.
181 /// This operation is O(N).
182 pub fn orderedRemove(self: *Self, i: usize) T {
183 const newlen = self.len - 1;
184 if (newlen == i) return self.pop();
185 const old_item = self.get(i);
186 for (self.slice()[i..newlen]) |*b, j| b.* = self.get(i + 1 + j);
187 self.set(newlen, undefined);
188 self.len = newlen;
189 return old_item;
190 }
191
192 /// Remove the element at the specified index and return it.
193 /// The empty slot is filled from the end of the slice.
194 /// This operation is O(1).
195 pub fn swapRemove(self: *Self, i: usize) T {
196 if (self.len - 1 == i) return self.pop();
197 const old_item = self.get(i);
198 self.set(i, self.pop());
199 return old_item;
200 }
201
202 /// Append the slice of items to the slice.
203 pub fn appendSlice(self: *Self, items: []const T) !void {
204 try self.ensureUnusedCapacity(items.len);
205 self.appendSliceAssumeCapacity(items);
206 }
207
208 /// Append the slice of items to the slice, asserting the capacity is already
209 /// enough to store the new items.
210 pub fn appendSliceAssumeCapacity(self: *Self, items: []const T) void {
211 const oldlen = self.len;
212 self.len += items.len;
213 mem.copy(T, self.slice()[oldlen..], items);
214 }
215
216 /// Append a value to the slice `n` times.
217 /// Allocates more memory as necessary.
218 pub fn appendNTimes(self: *Self, value: T, n: usize) !void {
219 const old_len = self.len;
220 try self.resize(old_len + n);
221 mem.set(T, self.slice()[old_len..self.len], value);
222 }
223
224 /// Append a value to the slice `n` times.
225 /// Asserts the capacity is enough.
226 pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void {
227 const old_len = self.len;
228 self.len += n;
229 assert(self.len <= capacity);
230 mem.set(T, self.slice()[old_len..self.len], value);
231 }
232 };
233}
234
235test "BoundedArray" {
236 var a = try BoundedArray(u8, 64).init(32);
237
238 try testing.expectEqual(a.capacity(), 64);
239 try testing.expectEqual(a.slice().len, 32);
240 try testing.expectEqual(a.constSlice().len, 32);
241
242 try a.resize(48);
243 try testing.expectEqual(a.len, 48);
244
245 const x = [_]u8{1} ** 10;
246 a = try BoundedArray(u8, 64).fromSlice(&x);
247 try testing.expectEqualSlices(u8, &x, a.constSlice());
248
249 var a2 = a;
250 try testing.expectEqualSlices(u8, a.constSlice(), a.constSlice());
251 a2.set(0, 0);
252 try testing.expect(a.get(0) != a2.get(0));
253
254 try testing.expectError(error.Overflow, a.resize(100));
255 try testing.expectError(error.Overflow, BoundedArray(u8, x.len - 1).fromSlice(&x));
256
257 try a.resize(0);
258 try a.ensureUnusedCapacity(a.capacity());
259 (try a.addOne()).* = 0;
260 try a.ensureUnusedCapacity(a.capacity() - 1);
261 try testing.expectEqual(a.len, 1);
262
263 const uninitialized = try a.addManyAsArray(4);
264 try testing.expectEqual(uninitialized.len, 4);
265 try testing.expectEqual(a.len, 5);
266
267 try a.append(0xff);
268 try testing.expectEqual(a.len, 6);
269 try testing.expectEqual(a.pop(), 0xff);
270
271 try a.resize(1);
272 try testing.expectEqual(a.popOrNull(), 0);
273 try testing.expectEqual(a.popOrNull(), null);
274 var unused = a.unusedCapacitySlice();
275 mem.set(u8, unused[0..8], 2);
276 unused[8] = 3;
277 unused[9] = 4;
278 try testing.expectEqual(unused.len, a.capacity());
279 try a.resize(10);
280
281 try a.insert(5, 0xaa);
282 try testing.expectEqual(a.len, 11);
283 try testing.expectEqual(a.get(5), 0xaa);
284 try testing.expectEqual(a.get(9), 3);
285 try testing.expectEqual(a.get(10), 4);
286
287 try a.appendSlice(&x);
288 try testing.expectEqual(a.len, 11 + x.len);
289
290 try a.appendNTimes(0xbb, 5);
291 try testing.expectEqual(a.len, 11 + x.len + 5);
292 try testing.expectEqual(a.pop(), 0xbb);
293
294 a.appendNTimesAssumeCapacity(0xcc, 5);
295 try testing.expectEqual(a.len, 11 + x.len + 5 - 1 + 5);
296 try testing.expectEqual(a.pop(), 0xcc);
297
298 try testing.expectEqual(a.len, 29);
299 try a.replaceRange(1, 20, &x);
300 try testing.expectEqual(a.len, 29 + x.len - 20);
301
302 try a.insertSlice(0, &x);
303 try testing.expectEqual(a.len, 29 + x.len - 20 + x.len);
304
305 try a.replaceRange(1, 5, &x);
306 try testing.expectEqual(a.len, 29 + x.len - 20 + x.len + x.len - 5);
307
308 try a.append(10);
309 try testing.expectEqual(a.pop(), 10);
310
311 try a.append(20);
312 const removed = a.orderedRemove(5);
313 try testing.expectEqual(removed, 1);
314 try testing.expectEqual(a.len, 34);
315
316 a.set(0, 0xdd);
317 a.set(a.len - 1, 0xee);
318 const swapped = a.swapRemove(0);
319 try testing.expectEqual(swapped, 0xdd);
320 try testing.expectEqual(a.get(0), 0xee);
321}
lib/std/std.zig+1
......@@ -13,6 +13,7 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap;
1313pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;
1414pub const AutoHashMap = hash_map.AutoHashMap;
1515pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;
16pub const BoundedArray = @import("bounded_array.zig").BoundedArray;
1617pub const BufMap = @import("buf_map.zig").BufMap;
1718pub const BufSet = @import("buf_set.zig").BufSet;
1819pub const ChildProcess = @import("child_process.zig").ChildProcess;