Idiomatic Complex Formatting

The idiomatic way of implementing custom formatting would be to implement a format function. std.fmt.format, which std.debug.print and other formatting functions that take a format string with placeholders are derived from, has this to say in its doc comment:

If a formatted user type contains a function of the type

pub fn format(
   value: ?,
   comptime fmt: []const u8,
   options: std.fmt.FormatOptions,
   writer: anytype,
) !void

with ? being the type formatted, this function will be called instead of the default implementation. This allows user types to be formatted in a logical manner instead of dumping all fields of the type.

This means that if your struct implements a format function, you can print it simply by passing it to a print function, like std.debug.print("{}", .{mat}). See std.SemanticVersion.format for one example of such a function.

A port of your original function would look something like this (writer is assumed to be a std.io.Writer):

pub fn format(
    mat: Matrix,
    comptime fmt: []const u8,
    options: std.fmt.FormatOptions,
    writer: anytype,
) !void {
    _ = fmt;
    _ = options;
    try writer.writeAll(@typeName(Matrix));
    try writer.writeAll("[\n");
    for (0..rows) |ridx| {
        try writer.writeAll("\t[");
        for (0..cols) |cidx| {
            try writer.print("{any}", .{mat.get(ridx, cidx)});
            if (cidx != cols - 1) {
                try writer.writeByte(',');
            }
        }
        try writer.writeByte(']');
        if (ridx != rows - 1) {
            try writer.writeAll(",\n");
        }
    }
    try writer.writeAll("]\n");
}

The format function doesn’t allocate unless the writer implementation allocates.

Addendum: If you don’t control the struct you want to format, you can implement a function that returns a std.fmt.Formatter. See std.zig.fmtId for an example.

3 Likes