Skip to main content
Version: Zig 0.12.0

Formatting

std.fmt provides ways to format data to and from strings.

A basic example of creating a formatted string. The format string must be compile-time known. The d here denotes that we want a decimal number.

const std = @import("std");
const expect = std.testing.expect;
const eql = std.mem.eql;
const test_allocator = std.testing.allocator;

test "fmt" {
const string = try std.fmt.allocPrint(
test_allocator,
"{d} + {d} = {d}",
.{ 9, 10, 19 },
);
defer test_allocator.free(string);

try expect(eql(u8, string, "9 + 10 = 19"));
}

Writers conveniently have a print method, which works similarly.

const std = @import("std");
const expect = std.testing.expect;
const eql = std.mem.eql;
const test_allocator = std.testing.allocator;
test "print" {
var list = std.ArrayList(u8).init(test_allocator);
defer list.deinit();
try list.writer().print(
"{} + {} = {}",
.{ 9, 10, 19 },
);
try expect(eql(u8, list.items, "9 + 10 = 19"));
}

Take a moment to appreciate that you now know from top to bottom how printing Hello World works. std.debug.print works the same, except it writes to stderr and is protected by a mutex.

const std = @import("std");
const expect = std.testing.expect;
const eql = std.mem.eql;
test "hello world" {
const out_file = std.io.getStdOut();
try out_file.writer().print(
"Hello, {s}!\n",
.{"World"},
);
}

We have used the {s} format specifier up until this point to print strings. Here, we will use {any}, which gives us the default formatting.

const std = @import("std");
const expect = std.testing.expect;
const eql = std.mem.eql;
const test_allocator = std.testing.allocator;
test "array printing" {
const string = try std.fmt.allocPrint(
test_allocator,
"{any} + {any} = {any}",
.{
@as([]const u8, &[_]u8{ 1, 4 }),
@as([]const u8, &[_]u8{ 2, 5 }),
@as([]const u8, &[_]u8{ 3, 9 }),
},
);
defer test_allocator.free(string);

try expect(eql(
u8,
string,
"{ 1, 4 } + { 2, 5 } = { 3, 9 }",
));
}

Let's create a type with custom formatting by giving it a format function. This function must be marked as pub so that std.fmt can access it (more on packages later). You may notice the usage of {s} instead of {} - this is the format specifier for strings (more on format specifiers later). This is used here as {} defaults to array printing over string printing.

const std = @import("std");
const expect = std.testing.expect;
const eql = std.mem.eql;
const test_allocator = std.testing.allocator;
const Person = struct {
name: []const u8,
birth_year: i32,
death_year: ?i32,
pub fn format(
self: Person,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;

try writer.print("{s} ({}-", .{
self.name, self.birth_year,
});

if (self.death_year) |year| {
try writer.print("{}", .{year});
}

try writer.writeAll(")");
}
};

test "custom fmt" {
const john = Person{
.name = "John Carmack",
.birth_year = 1970,
.death_year = null,
};

const john_string = try std.fmt.allocPrint(
test_allocator,
"{s}",
.{john},
);
defer test_allocator.free(john_string);

try expect(eql(
u8,
john_string,
"John Carmack (1970-)",
));

const claude = Person{
.name = "Claude Shannon",
.birth_year = 1916,
.death_year = 2001,
};

const claude_string = try std.fmt.allocPrint(
test_allocator,
"{s}",
.{claude},
);
defer test_allocator.free(claude_string);

try expect(eql(
u8,
claude_string,
"Claude Shannon (1916-2001)",
));
}