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)",
));
}