Format tricks

Now that Zig’s default magic format function has become more convenient in 0.15.1, I dediced to implement some in my program.

This writes some array to the writer (using the Move`s format as well):

pub fn format(self: Node, writer: *std.io.Writer) std.io.Writer.Error!void {
    const len: usize = self.pv.len;
    if (len == 0) return;

    for (self.pv.buffer[0..len - 1]) |move|  {
        try writer.print("{f} ", .{ move });
    }

    const last: Move = self.pv.buffer[len - 1];
    try writer.print("{f}", .{ last });

Notice how I have to split the code to prevent a trailing space.
In C# I was used to (for example) the easy string.join(separator, array) function.
Would there be an easier way to do this above function?

Another question:
I can imagine structs can have multiple formats. Is there a way to easily implement “custom formatters”?

1 Like

You can create format adapters.
e.g.

try print("{f}", .{Pretty{.data = my_data}});
...
const Pretty = struct {
    data: MyData;
    pub fn format(...) {
         ...
    }
};
5 Likes

you can also define multiple format functions, with different names ofc, and then do print("{f}", .{std.fmt.alt(mydata, .otherFmtFn)})

this and @dimdin suggestion and another can be found in the relevant section in the release notes

1 Like

Something I use sometimes is this:

var sep: []const u8 = "";
for (self.pv.buffer[0..len - 1]) |move| {
    try writer.print("{s}{f}", .{ sep, move });
    sep = " ";
}

That way the first separator is nothing and all the following ones are set to some other value.

2 Likes

Have you tried mem.join function?

I think you shouldn’t create a (potentially giant) temporary string just to then output it.

Instead you could write a helper function like this:

pub fn printJoin(
    w: *std.Io.Writer,
    separator: []const u8,
    /// must be a fmt for a single argument
    comptime element_fmt: []const u8,
    slice: anytype,
) std.Io.Writer.Error!void {
    if (slice.len == 0) return;
    try w.print(element_fmt, .{slice[0]});
    for (slice[1..]) |element| {
        try w.printAscii(separator, .{});
        try w.print(element_fmt, .{element});
    }
}

pub fn main() !void {
    var items = [_]Item{
        .stack(.cobbel, 5),
        .stack(.cobbel, 64),
        .stack(.dirt, 64),
        .stack(.dirt, 64),
        .stack(.dirt, 64),
        .stack(.dirt, 1),
        .stack(.dirt, 2),
    };
    const inventory: Inventory = .{ .items = &items };

    std.debug.print("inventory: {f}\n", .{inventory});
}

const Inventory = struct {
    items: []Item,

    pub fn format(
        self: Inventory,
        writer: *std.Io.Writer,
    ) std.Io.Writer.Error!void {
        try printJoin(writer, " ", "{f}", self.items);
    }
};

const Item = packed struct {
    const Kind = enum(u10) { dirt, cobbel };
    kind: Kind,
    amount: u6, // +1 equals count, absence is zero

    pub fn stack(kind: Kind, count: u7) Item {
        std.debug.assert(count != 0);
        std.debug.assert(count <= 64);
        return .{
            .kind = kind,
            .amount = @intCast(count - 1),
        };
    }

    pub fn format(
        self: Item,
        writer: *std.Io.Writer,
    ) std.Io.Writer.Error!void {
        const count = @as(u7, self.amount) + 1;
        try writer.print("{d}x{t}", .{ count, self.kind });
    }
};

const std = @import("std");
2 Likes

Something like your printJoin should be part of the std lib, though I don’t know in which module.

I think it would also be possible to replicate some of the logic in std.Io.Writer.print that detects the different placeholders in the format strings, then you could detect the placeholders that are within element_fmt and allow two different modes:

  1. there is only one placeholder and it doesn’t use named or positional arguments =>
    print with a single argument try w.print(element_fmt, .{element});
  2. there are multiple placeholders or there are placeholders using named or positional arguments =>
    print using named arguments try w.print(element_fmt, element);

(Or there could be two different functions printJoin and printJoinFields?)

That would allow something like this:

try printJoin(writer, "\n", "kind: {[kind]t} amount: {[amount]d}", self.items);

while also supporting:

try printJoin(writer, " ", "{f}", self.items);

One thing that seems inconvenient is that you can’t specify the name of the format function via the format string, so if you need that you would have to go back to writing your own code adding a format adapter to the argument passed to print.

But I think not having that potentially helps with keeping the number of generic instantiations of functions lower, still would be nice to have both convenience and low code duplication.

That said the amount would also either require a format adapter, or to be defined as its own type (for example exhaustive enum) with its own format function, if you want to print the count instead of the amount.