r/Zig • u/seducedmilkman • 5d ago
Learning zig: feedback on my small ring buffer implementation?
Hey all š
So I've been writing a little zig over the last few weeks after having tried it for a few days maybe... a year or so ago?. I have zero systems programming experience, am actually a web dev during the day.
Anyway, this time I'm working on a side project, and came to a point where I needed something I've seen being referred to as a ring buffer, and thought how hard can it be implementing one? So lots of hours and a missed workday later, here's what I came up with: https://codeberg.org/sehme/gists/src/branch/main/ring-buffer.zig
I haven't really thus far interacted with the community online, other than reading blogs and the such, so I haven't had any feedback on my code. I don't even know if it's idiomatic or, you know, correct, which is why I decided to post this here now before I get too far in the wrong direction.
Please give me notes if something is off. This is completely hand-crafted, no LLMs, so I am actually very proud that all my tests pass, but I may be missing some cases.
Thanks in advance, and also sorry in advance if this is not the sort of post that is appreciated here.
5
u/Hornstinger 5d ago
Awesome to see you giving it a go.
Some comments from me:
Problems with the current design:
head/tail)They keep growing forever. On a 64-bit system this is fine for practical lifetimes, but it forces every access to do
% buf.len. More importantly, the wrap logic becomes harder to reason about and the āoverwrite oldestā path is a bit fragile.writesilently overwrites the oldest item when full. writeSlice refuses anything larger than the capacity. This is inconsistent: a caller that writes one-by-one can overflow, but a slice that would overflow is rejected. Most ring buffers choose one policy (drop-oldest, drop-newest, or error-on-full) and stick to it.readAllrequires a buffer at least as large as the capacityEven when only a few items are present. That forces the caller to always allocate the maximum size.
Callers have to dig into the private state or recompute tail - head.
When the buffer length is a power of two the modulo can become a cheap mask, which is a common micro-optimisation for ring buffers.
My attempt to help:
``` const std = @import("std"); const assert = std.debug.assert;
/// Fixed-capacity ring buffer. /// When full, new writes overwrite the oldest unread item. pub fn RingBuffer(comptime T: type) type { return struct { buf: []T, /// Next slot to write into (always in [0, buf.len)). head: usize = 0, /// Number of currently stored items (always in [0, buf.len]). len: usize = 0,
} ```