I’m working on a ML inference platform in Zig. Tensors have a rank up to 8. This is a limitation of the compiler backend we are using.
So any metadata per axis of a tensor is stored in a 8-bounded array. Some functions need a lot of such metadata. Storing all those metadata in buffer + Len is more error prone, because it’s easy to pass the wrong len to the wrong buffer, or to forgot to increment a Len.
The buffer + ArrayList also litters the code with ‘undefined’ which I don’t love, and adds pointer indirection.
Most of those metadata can fit into a 256-bit register so resorting to pointers and slices through an ArrayList feels bad.
My main complaints with BoundedArray is the API was foreign to ArrayList making upgrading the code to it awkward.
I’m toying on a SIMD oriented “BoundedArray” for the case where they map well to large registers.
/// Initialize with externally-managed memory. The buffer determines the
/// capacity, and the length is set to zero.
///
/// When initialized this way, all functions that accept an Allocator
/// argument cause illegal behavior.
pub fn initBuffer(buffer: Slice) Self {
return .{
.items = buffer[0..0],
.capacity = buffer.len,
};
}
Examining some std code.
So this is a (new) footgun, I guess?
Just as a little brain dump, my recent chain-of-thought looks a bit like this:
new Zig programmers quite often seem to stumble over slices being references with the result of causing dangling-pointer-issues - my current pet theory about this is that the slice syntax looks a bit too ‘innocent’ for being essentially fat pointers…, it’s easy to mistake slices for value-types since most of the code that works on slices looks like it’s working on arrays - it definitely is interesting that most dangling reference problems I’ve seen here are about slices but not pointers(?)
…maybe one reason for this is that the syntax for slices heavily overlaps with the syntax for arrays (but I’m currently pushing that on the ‘think about this later’ stack of what such a more distinct slice syntax would look like)
from the opposite direction: maybe working with arrays needs to be a bit ‘nicer’ (e.g. see the C99 features for initializing struct literals with nested array - apologies for sounding like a broken record, but I just think that this particular feature area of C is particularly well designed and many other languages underestimate the importance of that feature)
The BoundedArray discussion is related to this whole issue of “working with arrays should be more convenient”, but it’s not at the centre.
After letting my thoughts settle on this issue a bit, I’ll write them out here. Apologies if I restate some things, but this thread is long and I’m not taking the time to read it all.
I was using BoundedArray for some buffers. I didn’t care about code bloat (because a bunch of buffers were all u8 and the same size), nor about limiting callers (because this isn’t a library, the callers are all coming from inside the house.) However, I have found that actually the new Reader and Writer are quite good. Using them is improving the code and naturally eliminating my current BoundedArray usages.
However, I do have a use case where I expect to use BoundedArray, but haven’t gotten to yet. I’m making a game, and doing networking by having a single state struct with a deterministic step function. To allow for rollback, the entire state struct is being copied. (eg for the unfamiliar, I save state, step forward for rendering my frame, then I get a network message that someone pressed a button last frame, so I rewind the state to before the button press then play it forward with the new info.)
So the entire game state sits inside one massive struct, and that struct is memcopied a lot. I could have dynamically sized arrays for different things, but:
I will be able to determine the maximum count for any given array member.
I want these limits to eliminate error handling requirements.
Inefficient copying isn’t actually an issue, because I care a lot about having a steady frame rate, and so the case where all of the resources are being utilized is actually my limiting factor. If I change my mind here, writing some comptime to do a deep copy is easy.
This system architecture creates limitations, but eliminates a large amount of complexity, which is the correct tradeoff for my project.
All of that said, I’ve just copied the old BoundedArray implementation into my project. I’m very likely going to be making changes to it, to better fit the ergonomics of my situation. Eg, I’ve already renamed it to be just Bounded. Even more, this is pretty speculative, as I’m still working on networking stuff. I’ll know more once I get to coding more complicated game logic.
Good point actually, I hadn’t even thought of this to be overloading, looks like I’m finally successful getting the C++ virus out of my system after a decade or so
Hey I have a usecase in microzig, we have an assembler for a bespoke hardware block, and BoundedArray made it a lot easier to implement the assembler so it could be used during comptime. You actually used this software in my synth workshop, behind the scenes it handled real-time communication for sending samples to the amplifier using the I2S protocol.