Can I sprintf to an ArrayList in Zig?

487 Views Asked by At

The Zig standard library contains the function std.fmt.allocPrint which takes in an allocator, prints to a buffer allocated with said allocator, and returns the result, much like a dynamic sprintf.

For my particular use-case, I need to concatenate multiple strings together after having printed to them. The only solution that I see, using allocPrint, is to first print to some allocated memory and then copy the bytes over to an ArrayList, which seems redundant.

Ideally, the standard library would include a function that allows one to print directly to an ArrayList, like the following:

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.assert(gpa.deinit() == .ok);
    const allocator = gpa.allocator();

    var list = std.ArrayList(u8).init(allocator);
    defer list.deinit();

    const values = [_]f64{ 5.6, 123.4, 19.6, 18.4 };

    for (values) |value| {
        try std.fmt.allocPrintToArrayList(list, "{e} ", .{value}); // Does not work
    }

    _ = list.pop();

    // Ideally `list.items` would include the `values` delimited by spaces
}

Is there a way of doing what I need in Zig? For example, maybe I could use ArrayList as an allocator so it can be, in turn, used with the allocPrint API.

1

There are 1 best solutions below

0
On BEST ANSWER

Printing to a Single String

I misunderstood the problem statement at first: I thought that OP wanted to collect strings in an ArrayList, but it seems that OP wants to create a single string by printing values followed by delimiters. The original solution is at the end of this answer.

ArrayList has a writer method which appends to the end of a list. You can combine that with a bit of logic to print delimiters to arrive at the desired result.

const std = @import("std");
const allocPrint = std.fmt.allocPrint;
const append = std.ArrayList.append;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer std.debug.assert(gpa.deinit() == .ok);

    var list = std.ArrayList(u8).init(allocator);
    defer list.deinit();

    const values = [_]f64{ 5.6, 123.4, 19.6, 18.4 };

    const delimiter = " ";
    for (values, 1..) |value, i| {
        try list.writer().print(
            "{e}{s}",
            .{ value, if (i < values.len) delimiter else "" },
        );
    }

    std.debug.print("{s}\n", .{list.items});
}
> .\arraylist_string
5.6e+00 1.234e+02 1.96e+01 1.84e+01

The Original Answer: Creating a List of Strings

You can use ArrayList.append to append the result of calling allocPrint to the ArrayList since allocPrint returns a slice of the bytes which were printed. Since allocPrint makes an allocation each time it is called these will also need to be freed.

The ArrayList will also need to hold []u8 types instead of u8 types.

const std = @import("std");
const allocPrint = std.fmt.allocPrint;
const append = std.ArrayList.append;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer std.debug.assert(gpa.deinit() == .ok);

    var list = std.ArrayList([]u8).init(allocator);
    defer list.deinit();

    const values = [_]f64{ 5.6, 123.4, 19.6, 18.4 };

    for (values) |value| {
        try list.append(
            try (allocPrint(allocator, "{e} ", .{value})),
        );
    }
    // free strings created by `allocPrint`
    defer {
        for (list.items) |s| {
            allocator.free(s);
        }
    }

    for (list.items) |s| {
        std.debug.print("{s}\n", .{s});
    }
}
> .\arraylist_strings
5.6e+00
1.234e+02
1.96e+01
1.84e+01