How to clear/flush buffer (ArrayList/std.heap.FixedBufferAllocator)

My goal was to make a buffer that I can flush/clear when it is getting full, but I guess I don’t fully understand how ArrayList and/or std.heap.FixedBufferAllocator work.

Here’s what I tried:

const std = @import("std");

const ArrayList = std.ArrayList;

pub fn main() !void {
    var buf: [64]u8 = undefined;
    @memset(buf[0..], 0);
    var FBA = std.heap.FixedBufferAllocator.init(&buf);
    defer FBA.reset();
    var list = ArrayList(u8).init(FBA.allocator());
    defer list.deinit();
    // FBA.reset();
    try list.append('H');
    try list.append('e');
    try list.append('l');
    try list.append('l');
    try list.append('o');
    try list.append(' ');
    try list.appendSlice("é");
    try list.appendSlice("€");
    try list.appendSlice(" World!");

    std.debug.print("{s}\n", .{list.items});
    std.debug.print("{}\n", .{list.capacity});
    std.debug.print("{s}\n", .{buf});
    std.debug.print("{any}\n", .{FBA});
    
    //FBA.reset();
    @memset(buf[0..], 0);
    //FBA = FBA{.end_index=0, .buffer=&buf};
    
    try list.append('H');
    try list.append('e');
    try list.append('l');
    try list.append('l');
    try list.append('o');
    try list.append(' ');
    try list.appendSlice("é");
    try list.appendSlice("€");
    try list.appendSlice(" World!");

    std.debug.print("{s}\n", .{list.items});
    std.debug.print("{}\n", .{list.capacity});
    std.debug.print("{s}\n", .{buf});
    std.debug.print("{any}\n", .{FBA});
}

I commented out some of what I tried and what absolutely will crash the build.

Here’s the output of running the code:

Hello é€ World!
20
Hello é€ World!
heap.FixedBufferAllocator{ .end_index = 20, .buffer = { 72, 101, 108, 108, 111, 32, 195, 169, 226, 130, 172, 32, 87, 111, 114, 108, 100, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }
Hello é€ World!
38
Hello é€ World!
heap.FixedBufferAllocator{ .end_index = 38, .buffer = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 195, 169, 226, 130, 172, 32, 87, 111, 114, 108, 100, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }

Is there a way to reset the .end_index? Because if I zero the buffer string and then append again, it obviously starts from where it stopped before and not from beginning.

Any thoughts?
What should I do instead?

Not really sure what are you trying to do but maybe you can look at std.BoundedArray

Using it code above will look like this:

const std = @import("std");

pub fn main() !void {
    var list: std.BoundedArray(u8, 64) = .{};
    try list.append('H');
    try list.append('e');
    try list.append('l');
    try list.append('l');
    try list.append('o');
    try list.append(' ');
    try list.appendSlice("é");
    try list.appendSlice("€");
    try list.appendSlice(" World!");

    std.debug.print("{s}\n", .{list.slice()});

    list.clear();

    try list.append('H');
    try list.append('e');
    try list.append('l');
    try list.append('l');
    try list.append('o');
    try list.append(' ');
    try list.appendSlice("é");
    try list.appendSlice("€");
    try list.appendSlice(" World!");

    std.debug.print("{s}\n", .{list.slice()});
}

Program prints:

Hello é€ World!
Hello é€ World!

Awesome! this might actually work for what I wanted to do.

Unfortunately in zig 0.13.0 BoundedArray does not have method clear.

OK, it works if I resize it to zero:

const std = @import("std");

pub fn main() !void {
    var list: std.BoundedArray(u8, 64) = .{};
    try list.append('H');
    try list.append('e');
    try list.append('l');
    try list.append('l');
    try list.append('o');
    try list.append(' ');
    try list.appendSlice("é");
    try list.appendSlice("€");
    try list.appendSlice(" World!");

    std.debug.print("{s}\n", .{list.slice()});

    //list.clear();
    try list.resize(0);

    try list.append('H');
    try list.append('e');
    try list.append('l');
    try list.append('l');
    try list.append('o');
    try list.append(' ');
    try list.appendSlice("é");
    try list.appendSlice("€");
    try list.appendSlice(" World!");

    std.debug.print("{s}\n", .{list.slice()});
}

Only slightly related to your question, but note that using a FixedBufferAllocator for an ArrayList will likely not allow you to use all of the available buffer, since ArrayList could try to grow larger than the available buffer length even if you end up writing <= the buffer length (since ArrayList grows super-linearly).

const std = @import("std");

test "fba arraylist" {
    var buf: [64]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buf);
    var list = std.ArrayList(u8).init(fba.allocator());
    defer list.deinit();

    for (0..buf.len) |_| {
        try list.append('a');
    }

    try std.testing.expectEqual(buf.len, list.items.len);
}
1/1 fba_arraylist.test.fba arraylist...FAIL (OutOfMemory)

An alternative would be to used FixedBufferStream and writer instead:

test "fbs" {
    var buf: [64]u8 = undefined;
    var fbs = std.io.fixedBufferStream(&buf);
    const writer = fbs.writer();

    for (0..buf.len) |_| {
        try writer.writeByte('a');
    }

    try std.testing.expectEqual(buf.len, fbs.getWritten().len);
}
All 1 tests passed.
1 Like