aboutsummaryrefslogtreecommitdiff
path: root/lib/std/array_list.zig
diff options
context:
space:
mode:
authorCameron Conn <camconn@users.noreply.github.com>2021-01-02 18:06:51 -0600
committerGitHub <noreply@github.com>2021-01-02 19:06:51 -0500
commitdb1e97d4b19d8399252e0fbc85fc3563b005a892 (patch)
tree065247f953f6ea6f8926bc555d63d9472967cb4a /lib/std/array_list.zig
parent1856dfea6b797d852b496c2111dbb326dbe2957e (diff)
downloadzig-db1e97d4b19d8399252e0fbc85fc3563b005a892.tar.gz
zig-db1e97d4b19d8399252e0fbc85fc3563b005a892.zip
Improve documentation for ArrayList, ArrayListUnmanaged, etc. (#7624)
* Improve ArrayList & co documentation - Added doc comments about the validity of references to elements in an ArrayList and how they may become invalid after resizing operations. - This should help users avoid footguns in future. * Improve ArrayListUnmanaged & co's documentation - Port improved documentation from ArrayList and ArrayList aligned to their unmanaged counterparts. - Made documentation for ArrayListUnmanaged & co more inclusive and up-to-date. - Made documentation more consistent with `ArrayList`. * Corrections on ArrayList documentation. - Remove incorrect/unpreferred wording on ArrayList vs ArrayListUnmanaged. - Fix notes about the alignment of ArrayListAligned - Be more verbose with warnings on when pointers are invalidated. - Copy+paste a few warnings * add warning to replaceRange * revert changes to append documentation
Diffstat (limited to 'lib/std/array_list.zig')
-rw-r--r--lib/std/array_list.zig142
1 files changed, 100 insertions, 42 deletions
diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig
index ccdf487f48..3114d1b744 100644
--- a/lib/std/array_list.zig
+++ b/lib/std/array_list.zig
@@ -12,10 +12,20 @@ const Allocator = mem.Allocator;
/// A contiguous, growable list of items in memory.
/// This is a wrapper around an array of T values. Initialize with `init`.
+///
+/// This struct internally stores a `std.mem.Allocator` for memory management.
+/// To manually specify an allocator with each method call see `ArrayListUnmanaged`.
pub fn ArrayList(comptime T: type) type {
return ArrayListAligned(T, null);
}
+/// A contiguous, growable list of arbitrarily aligned items in memory.
+/// This is a wrapper around an array of T values aligned to `alignment`-byte
+/// addresses. If the specified alignment is `null`, then `@alignOf(T)` is used.
+/// Initialize with `init`.
+///
+/// This struct internally stores a `std.mem.Allocator` for memory management.
+/// To manually specify an allocator with each method call see `ArrayListAlignedUnmanaged`.
pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
if (alignment) |a| {
if (a == @alignOf(T)) {
@@ -24,9 +34,18 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
}
return struct {
const Self = @This();
-
- /// Content of the ArrayList
+ /// Contents of the list. Pointers to elements in this slice are
+ /// **invalid after resizing operations** on the ArrayList, unless the
+ /// operation explicitly either: (1) states otherwise or (2) lists the
+ /// invalidated pointers.
+ ///
+ /// The allocator used determines how element pointers are
+ /// invalidated, so the behavior may vary between lists. To avoid
+ /// illegal behavior, take into account the above paragraph plus the
+ /// explicit statements given in each method.
items: Slice,
+ /// How many T values this list can hold without allocating
+ /// additional memory.
capacity: usize,
allocator: *Allocator,
@@ -42,7 +61,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
};
}
- /// Initialize with capacity to hold at least num elements.
+ /// Initialize with capacity to hold at least `num` elements.
/// Deinitialize with `deinit` or use `toOwnedSlice`.
pub fn initCapacity(allocator: *Allocator, num: usize) !Self {
var self = Self.init(allocator);
@@ -79,11 +98,13 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
};
}
+ /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields
+ /// of this ArrayList. This ArrayList retains ownership of underlying memory.
pub fn toUnmanaged(self: Self) ArrayListAlignedUnmanaged(T, alignment) {
return .{ .items = self.items, .capacity = self.capacity };
}
- /// The caller owns the returned memory. ArrayList becomes empty.
+ /// The caller owns the returned memory. Empties this ArrayList.
pub fn toOwnedSlice(self: *Self) Slice {
const allocator = self.allocator;
const result = allocator.shrink(self.allocatedSlice(), self.items.len);
@@ -91,7 +112,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
return result;
}
- /// The caller owns the returned memory. ArrayList becomes empty.
+ /// The caller owns the returned memory. Empties this ArrayList.
pub fn toOwnedSliceSentinel(self: *Self, comptime sentinel: T) ![:sentinel]T {
try self.append(sentinel);
const result = self.toOwnedSlice();
@@ -118,9 +139,10 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
mem.copy(T, self.items[i .. i + items.len], items);
}
- /// Replace range of elements `list[start..start+len]` with `new_items`
- /// grows list if `len < new_items.len`. may allocate
- /// shrinks list if `len > new_items.len`
+ /// Replace range of elements `list[start..start+len]` with `new_items`.
+ /// Grows list if `len < new_items.len`.
+ /// Shrinks list if `len > new_items.len`.
+ /// Invalidates pointers if this ArrayList is resized.
pub fn replaceRange(self: *Self, start: usize, len: usize, new_items: SliceConst) !void {
const after_range = start + len;
const range = self.items[start..after_range];
@@ -151,15 +173,18 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
new_item_ptr.* = item;
}
- /// Extend the list by 1 element, but asserting `self.capacity`
- /// is sufficient to hold an additional item.
+ /// Extend the list by 1 element, but assert `self.capacity`
+ /// is sufficient to hold an additional item. **Does not**
+ /// invalidate pointers.
pub fn appendAssumeCapacity(self: *Self, item: T) void {
const new_item_ptr = self.addOneAssumeCapacity();
new_item_ptr.* = item;
}
- /// Remove the element at index `i` from the list and return its value.
+ /// Remove the element at index `i`, shift elements after index
+ /// `i` forward, and return the removed element.
/// Asserts the array has at least one item.
+ /// Invalidates pointers to end of list.
/// This operation is O(N).
pub fn orderedRemove(self: *Self, i: usize) T {
const newlen = self.items.len - 1;
@@ -191,7 +216,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
}
/// Append the slice of items to the list, asserting the capacity is already
- /// enough to store the new items.
+ /// enough to store the new items. **Does not** invalidate pointers.
pub fn appendSliceAssumeCapacity(self: *Self, items: SliceConst) void {
const oldlen = self.items.len;
const newlen = self.items.len + items.len;
@@ -227,7 +252,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
}
/// Append a value to the list `n` times.
- /// Asserts the capacity is enough.
+ /// Asserts the capacity is enough. **Does not** invalidate pointers.
pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void {
const new_len = self.items.len + n;
assert(new_len <= self.capacity);
@@ -243,7 +268,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
}
/// Reduce allocated capacity to `new_len`.
- /// Invalidates element pointers.
+ /// May invalidate element pointers.
pub fn shrink(self: *Self, new_len: usize) void {
assert(new_len <= self.items.len);
@@ -257,13 +282,14 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
}
/// Reduce length to `new_len`.
- /// Invalidates element pointers.
- /// Keeps capacity the same.
+ /// Invalidates pointers for the elements `items[new_len..]`.
pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void {
assert(new_len <= self.items.len);
self.items.len = new_len;
}
+ /// Modify the array so that it can hold at least `new_capacity` items.
+ /// Invalidates pointers if additional memory is needed.
pub fn ensureCapacity(self: *Self, new_capacity: usize) !void {
var better_capacity = self.capacity;
if (better_capacity >= new_capacity) return;
@@ -280,14 +306,13 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
}
/// Increases the array's length to match the full capacity that is already allocated.
- /// The new elements have `undefined` values. This operation does not invalidate any
- /// element pointers.
+ /// The new elements have `undefined` values. **Does not** invalidate pointers.
pub fn expandToCapacity(self: *Self) void {
self.items.len = self.capacity;
}
/// Increase length by 1, returning pointer to the new item.
- /// The returned pointer becomes invalid when the list is resized.
+ /// The returned pointer becomes invalid when the list resized.
pub fn addOne(self: *Self) !*T {
const newlen = self.items.len + 1;
try self.ensureCapacity(newlen);
@@ -297,6 +322,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// Increase length by 1, returning pointer to the new item.
/// Asserts that there is already space for the new item without allocating more.
/// The returned pointer becomes invalid when the list is resized.
+ /// **Does not** invalidate element pointers.
pub fn addOneAssumeCapacity(self: *Self) *T {
assert(self.items.len < self.capacity);
@@ -306,6 +332,8 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// Resize the array, adding `n` new elements, which have `undefined` values.
/// The return value is an array pointing to the newly allocated elements.
+ /// The returned pointer becomes invalid when the list is resized.
+ /// Resizes list if `self.capacity` is not large enough.
pub fn addManyAsArray(self: *Self, comptime n: usize) !*[n]T {
const prev_len = self.items.len;
try self.resize(self.items.len + n);
@@ -315,6 +343,8 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// Resize the array, adding `n` new elements, which have `undefined` values.
/// The return value is an array pointing to the newly allocated elements.
/// Asserts that there is already space for the new item without allocating more.
+ /// **Does not** invalidate element pointers.
+ /// The returned pointer becomes invalid when the list is resized.
pub fn addManyAsArrayAssumeCapacity(self: *Self, comptime n: usize) *[n]T {
assert(self.items.len + n <= self.capacity);
const prev_len = self.items.len;
@@ -324,21 +354,23 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// Remove and return the last element from the list.
/// Asserts the list has at least one item.
+ /// Invalidates pointers to the removed element.
pub fn pop(self: *Self) T {
const val = self.items[self.items.len - 1];
self.items.len -= 1;
return val;
}
- /// Remove and return the last element from the list.
- /// If the list is empty, returns `null`.
+ /// Remove and return the last element from the list, or
+ /// return `null` if list is empty.
+ /// Invalidates pointers to the removed element, if any.
pub fn popOrNull(self: *Self) ?T {
if (self.items.len == 0) return null;
return self.pop();
}
/// Returns a slice of all the items plus the extra capacity, whose memory
- /// contents are undefined.
+ /// contents are `undefined`.
pub fn allocatedSlice(self: Self) Slice {
// For a nicer API, `items.len` is the length, not the capacity.
// This requires "unsafe" slicing.
@@ -346,7 +378,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
}
/// Returns a slice of only the extra capacity after items.
- /// This can be useful for writing directly into an `ArrayList`.
+ /// This can be useful for writing directly into an ArrayList.
/// Note that such an operation must be followed up with a direct
/// modification of `self.items.len`.
pub fn unusedCapacitySlice(self: Self) Slice {
@@ -355,12 +387,18 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
};
}
-/// Bring-your-own allocator with every function call.
-/// Initialize directly and deinitialize with `deinit` or use `toOwnedSlice`.
+/// An ArrayList, but the allocator is passed as a parameter to the relevant functions
+/// rather than stored in the struct itself. The same allocator **must** be used throughout
+/// the entire lifetime of an ArrayListUnmanaged. Initialize directly or with
+/// `initCapacity`, and deinitialize with `deinit` or use `toOwnedSlice`.
pub fn ArrayListUnmanaged(comptime T: type) type {
return ArrayListAlignedUnmanaged(T, null);
}
+/// An ArrayListAligned, but the allocator is passed as a parameter to the relevant
+/// functions rather than stored in the struct itself. The same allocator **must**
+/// be used throughout the entire lifetime of an ArrayListAlignedUnmanaged.
+/// Initialize directly or with `initCapacity`, and deinitialize with `deinit` or use `toOwnedSlice`.
pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) type {
if (alignment) |a| {
if (a == @alignOf(T)) {
@@ -369,9 +407,18 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
}
return struct {
const Self = @This();
-
- /// Content of the ArrayList.
+ /// Contents of the list. Pointers to elements in this slice are
+ /// **invalid after resizing operations** on the ArrayList, unless the
+ /// operation explicitly either: (1) states otherwise or (2) lists the
+ /// invalidated pointers.
+ ///
+ /// The allocator used determines how element pointers are
+ /// invalidated, so the behavior may vary between lists. To avoid
+ /// illegal behavior, take into account the above paragraph plus the
+ /// explicit statements given in each method.
items: Slice = &[_]T{},
+ /// How many T values this list can hold without allocating
+ /// additional memory.
capacity: usize = 0,
pub const Slice = if (alignment) |a| ([]align(a) T) else []T;
@@ -395,6 +442,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
self.* = undefined;
}
+ /// Convert this list into an analogous memory-managed one.
+ /// The returned list has ownership of the underlying memory.
pub fn toManaged(self: *Self, allocator: *Allocator) ArrayListAligned(T, alignment) {
return .{ .items = self.items, .capacity = self.capacity, .allocator = allocator };
}
@@ -414,7 +463,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
}
/// Insert `item` at index `n`. Moves `list[n .. list.len]`
- /// to make room.
+ /// to higher indices to make room.
+ /// This operation is O(N).
pub fn insert(self: *Self, allocator: *Allocator, n: usize, item: T) !void {
try self.ensureCapacity(allocator, self.items.len + 1);
self.items.len += 1;
@@ -423,8 +473,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
self.items[n] = item;
}
- /// Insert slice `items` at index `i`. Moves
- /// `list[i .. list.len]` to make room.
+ /// Insert slice `items` at index `i`. Moves `list[i .. list.len]` to
+ /// higher indicices make room.
/// This operation is O(N).
pub fn insertSlice(self: *Self, allocator: *Allocator, i: usize, items: SliceConst) !void {
try self.ensureCapacity(allocator, self.items.len + items.len);
@@ -435,8 +485,9 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
}
/// Replace range of elements `list[start..start+len]` with `new_items`
- /// grows list if `len < new_items.len`. may allocate
- /// shrinks list if `len > new_items.len`
+ /// Grows list if `len < new_items.len`.
+ /// Shrinks list if `len > new_items.len`
+ /// Invalidates pointers if this ArrayList is resized.
pub fn replaceRange(self: *Self, allocator: *Allocator, start: usize, len: usize, new_items: SliceConst) !void {
var managed = self.toManaged(allocator);
try managed.replaceRange(start, len, new_items);
@@ -457,7 +508,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
}
/// Remove the element at index `i` from the list and return its value.
- /// Asserts the array has at least one item.
+ /// Asserts the array has at least one item. Invalidates pointers to
+ /// last element.
/// This operation is O(N).
pub fn orderedRemove(self: *Self, i: usize) T {
const newlen = self.items.len - 1;
@@ -472,6 +524,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// Removes the element at the specified index and returns it.
/// The empty slot is filled from the end of the list.
+ /// Invalidates pointers to last element.
/// This operation is O(1).
pub fn swapRemove(self: *Self, i: usize) T {
if (self.items.len - 1 == i) return self.pop();
@@ -515,6 +568,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
}
/// Append a value to the list `n` times.
+ /// **Does not** invalidate pointers.
/// Asserts the capacity is enough.
pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void {
const new_len = self.items.len + n;
@@ -524,14 +578,13 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
}
/// Adjust the list's length to `new_len`.
- /// Does not initialize added items if any.
+ /// Does not initialize added items, if any.
pub fn resize(self: *Self, allocator: *Allocator, new_len: usize) !void {
try self.ensureCapacity(allocator, new_len);
self.items.len = new_len;
}
/// Reduce allocated capacity to `new_len`.
- /// Invalidates element pointers.
pub fn shrink(self: *Self, allocator: *Allocator, new_len: usize) void {
assert(new_len <= self.items.len);
@@ -545,13 +598,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
}
/// Reduce length to `new_len`.
- /// Invalidates element pointers.
+ /// Invalidates pointers to elements `items[new_len..]`.
/// Keeps capacity the same.
pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void {
assert(new_len <= self.items.len);
self.items.len = new_len;
}
+ /// Modify the array so that it can hold at least `new_capacity` items.
+ /// Invalidates pointers if additional memory is needed.
pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void {
var better_capacity = self.capacity;
if (better_capacity >= new_capacity) return;
@@ -568,13 +623,13 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// Increases the array's length to match the full capacity that is already allocated.
/// The new elements have `undefined` values.
- /// This operation does not invalidate any element pointers.
+ /// **Does not** invalidate pointers.
pub fn expandToCapacity(self: *Self) void {
self.items.len = self.capacity;
}
/// Increase length by 1, returning pointer to the new item.
- /// The returned pointer becomes invalid when the list is resized.
+ /// The returned pointer becomes invalid when the list resized.
pub fn addOne(self: *Self, allocator: *Allocator) !*T {
const newlen = self.items.len + 1;
try self.ensureCapacity(allocator, newlen);
@@ -583,8 +638,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// Increase length by 1, returning pointer to the new item.
/// Asserts that there is already space for the new item without allocating more.
- /// The returned pointer becomes invalid when the list is resized.
- /// This operation does not invalidate any element pointers.
+ /// **Does not** invalidate pointers.
+ /// The returned pointer becomes invalid when the list resized.
pub fn addOneAssumeCapacity(self: *Self) *T {
assert(self.items.len < self.capacity);
@@ -594,6 +649,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// Resize the array, adding `n` new elements, which have `undefined` values.
/// The return value is an array pointing to the newly allocated elements.
+ /// The returned pointer becomes invalid when the list is resized.
pub fn addManyAsArray(self: *Self, allocator: *Allocator, comptime n: usize) !*[n]T {
const prev_len = self.items.len;
try self.resize(allocator, self.items.len + n);
@@ -603,6 +659,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// Resize the array, adding `n` new elements, which have `undefined` values.
/// The return value is an array pointing to the newly allocated elements.
/// Asserts that there is already space for the new item without allocating more.
+ /// **Does not** invalidate pointers.
+ /// The returned pointer becomes invalid when the list is resized.
pub fn addManyAsArrayAssumeCapacity(self: *Self, comptime n: usize) *[n]T {
assert(self.items.len + n <= self.capacity);
const prev_len = self.items.len;
@@ -612,7 +670,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// Remove and return the last element from the list.
/// Asserts the list has at least one item.
- /// This operation does not invalidate any element pointers.
+ /// Invalidates pointers to last element.
pub fn pop(self: *Self) T {
const val = self.items[self.items.len - 1];
self.items.len -= 1;
@@ -621,7 +679,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// Remove and return the last element from the list.
/// If the list is empty, returns `null`.
- /// This operation does not invalidate any element pointers.
+ /// Invalidates pointers to last element.
pub fn popOrNull(self: *Self) ?T {
if (self.items.len == 0) return null;
return self.pop();