Skip to content

remove RingBuffer; remove BoundedArray; use @memmove #24699

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 5, 2025
Merged

Conversation

andrewrk
Copy link
Member

@andrewrk andrewrk commented Aug 5, 2025

Progress towards #19231

Closes #19954

Upgrade Guide

ArrayListUnmanaged now has "Bounded" variants of all the "AssumeCapacity" methods:

-    var stack = try std.BoundedArray(i32, 8).fromSlice(initial_stack);
+    var buffer: [8]i32 = undefined;
+    var stack = std.ArrayListUnmanaged(i32).initBuffer(&buffer);
+    try stack.appendSliceBounded(initial_stack);

@andrewrk andrewrk added breaking Implementing this issue could cause existing code to no longer compile or have different behavior. standard library This issue involves writing Zig code for the standard library. release notes This PR should be mentioned in the release notes. labels Aug 5, 2025
@andrewrk andrewrk enabled auto-merge August 5, 2025 07:10
Copy link
Member

@ifreund ifreund left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soon there will be one ring buffer to rule them all!

@leecannon
Copy link
Contributor

leecannon commented Aug 5, 2025

Losing BoundedArray from std is a shame as ArrayListUnmanaged + a buffer is not safely copiable.

I also have structs containing BoundedArray so that an external allocation of short arrays is not necessary.

@@ -782,12 +798,29 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
assert(self.capacity >= new_len);
const to_move = self.items[index..];
self.items.len = new_len;
mem.copyBackwards(T, self.items[index + count ..], to_move);
@memmove(self.items[index + count ..][0..to_move.len], to_move);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why extra [0..to_move.len]? It provides no new asserts since memmove already checks for length equality

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the dest could be longer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is napkin math to prove it can't be longer

const old_len = self.items.len;
const new_len = old_len + count;
const to_move_len = old_len - index;
self.items.len = new_len;
self.items[index + count ..].len == new_len - index - count == old_len + count - index - count == old_len - index == to_move_len == to_move.len

@andrewrk
Copy link
Member Author

andrewrk commented Aug 5, 2025

Losing BoundedArray from std is a shame as ArrayListUnmanaged + a buffer is not safely copiable.

BoundedArray was safely copyable in the sense that it technically worked, but that was never really a wise operation to do since it pointlessly copied all the undefined items after len.

This use case is handled by ArrayListUnmanaged via the "...Bounded"
method variants, and it's more optimal to share machine code, versus
generating multiple versions of each function for differing array
lengths.
this function is wacky, should not have been merged
Progress towards #19231
@jedisct1
Copy link
Contributor

jedisct1 commented Aug 5, 2025

Losing BoundedArray from std is a shame as ArrayListUnmanaged + a buffer is not safely copiable.

BoundedArray was safely copyable in the sense that it technically worked, but that was never really a wise operation to do since it pointlessly copied all the undefined items after len.

Was it a problem? It was really intended for use with short arrays.

I'm really going to miss the convenience of BoundedArray.

@andrewrk
Copy link
Member Author

andrewrk commented Aug 5, 2025

Was it a problem? It was really intended for use with short arrays.

Yes, it was a problem in the sense that it reduced friction too much. In this diff you can see how it was used by the autodoc markdown parsing function to avoid allocations, but in exchange ended up doing a lot of unnecessary work to the point where heap allocation would have been better.

I'm really going to miss the convenience of BoundedArray.

And you are not alone. I am aware this is a controversial change. I recommend to copy it into your application, or create a reusable package for the ecosystem.

@jedisct1
Copy link
Contributor

jedisct1 commented Aug 5, 2025

The ArrayListUnmanaged type becomes quite confusing; not just because of the growing number of functions, but also due to the fact that different subsets of these functions are applicable depending on whether it's initialized with initCapacity() or initBuffer().

When initialized using initBuffer(), the language server and documentation will still suggest functions that require an allocator, such as append(), simply because they exist.

However, these functions make no sense in that context, which adds to the confusion.

Having separate types, one for unbounded lists that use an allocator, and another for lists backed by a static buffer, would help reduce this ambiguity.

Previously, BoundedArray helped prevent issues like dangling pointers. Removing it introduces a significant footgun that could be extremely difficult to debug.

BoundedArray is already available as a separate package here: https://github.com/jedisct1/zig-bounded-array

However, this puts Zig in an unfortunate situation, similar to what we’ve seen in the Rust ecosystem: where third-party dependencies are needed for extremely basic and common tasks. That, in turn, raises concerns about what happens when those dependencies break, change, become abandoned, or even get compromised.

@andrewrk
Copy link
Member Author

andrewrk commented Aug 5, 2025

However, this puts Zig in an unfortunate situation, similar to what we’ve seen in the Rust ecosystem: where third-party dependencies are needed for extremely basic and common tasks. That, in turn, raises concerns about what happens when those dependencies break, change, become abandoned, or even get compromised.

Well, we disagree on how basic and common the task is for this data structure, particularly when combined with other idiomatic practices. I personally would never add a dependency on that package, directly or transitively. While I understand and respect your opinion, and I recognize that mine is also just an opinion, ultimately I'm responsible for this stuff, I suffer the most consequences from these decisions, and so I must go with my conviction on this one.

@andrewrk
Copy link
Member Author

andrewrk commented Aug 5, 2025

Although I still plan to merge this, I am happy to continue the discussion.

I recommend to copy it into your application, or create a reusable package for the ecosystem.

I apologize; this is a cop-out and not a true recommendation. To be honest, I recommend to look at your code carefully and categorize it based on where the limit comes from:

  • Is it an arbitrary limit for which the BoundedArray usage is making a reasonable guess at the upper bound, or deciding resource limits? Don't guess. Don't make that choice for the calling code. Accept a buffer as a slice as an input, or use dynamic allocation. (example: the markdown code in this diff)
  • Is it type safety around a stack buffer? Just use ArrayListUnmanaged. It's fine. It's actually really convenient that this same data structure works here. (example: test_switch_dispatch_loop.zig in this diff)
  • Is it an ordered set with a well-defined maximum capacity? Quite rare. Just free-code it. (example: changes to Zcu.zig in this diff)
  • Is it being used as a growable array that can be copied? Bad idea, you are wasting time copying undefined memory all over the place and causing unnecessary generic code bloat.

@andrewrk andrewrk merged commit d8cecff into master Aug 5, 2025
12 checks passed
@andrewrk andrewrk deleted the bounded branch August 5, 2025 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking Implementing this issue could cause existing code to no longer compile or have different behavior. release notes This PR should be mentioned in the release notes. standard library This issue involves writing Zig code for the standard library.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BoundedArray segfaults for items larger than stack
5 participants