authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-25 12:07:21-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-25 18:54:36-07:00
logea151030bc4a1366ef3ca0bd1f59d4867a215762
tree0af291d969bff89de2825221c52d65fa0b5abfc6
parent65c96c403589607f882468891c4e2c329a28fde9

std.Io.Writer: implement {q} formatter

This is intended to replace the common pattern: print("invalid foobar: '{s}': {t}", ...) The idea is to not invent imaginary syntax. If the string contained single quotes for example, this would be a nonsensical error message. On the other hand with the new pattern: print("invalid foobar: {q}: {t}", ...) It's both easier on the eyes at the print site, and also it will allow the user to copy paste a properly escaped string, should the quoted text contain any odd characters, including invisible ones like null bytes.

1 files changed, 16 insertions(+), 14 deletions(-)

lib/std/Io/Writer.zig+16-14
......@@ -1172,23 +1172,12 @@ pub fn printValue(
11721172 },
11731173 else => invalidFmtError(fmt, value),
11741174 },
1175 // TODO make this print double quotes and quote-escape the string
1176 // according to zig string syntax rules
11771175 'q' => switch (@typeInfo(T)) {
11781176 .pointer => |info| switch (info.size) {
1179 .one, .slice => {
1180 const slice: []const u8 = value;
1181 return w.alignBufferOptions(slice, options);
1182 },
1183 .many, .c => {
1184 const slice: [:0]const u8 = std.mem.span(value);
1185 return w.alignBufferOptions(slice, options);
1186 },
1187 },
1188 .array => {
1189 const slice: []const u8 = &value;
1190 return w.alignBufferOptions(slice, options);
1177 .one, .slice => return printStringEscaped(w, value),
1178 .many, .c => return printStringEscaped(w, std.mem.span(value)),
11911179 },
1180 .array => return printStringEscaped(w, &value),
11921181 else => invalidFmtError(fmt, value),
11931182 },
11941183 'B' => switch (@typeInfo(T)) {
......@@ -1467,6 +1456,14 @@ fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void {
14671456 try w.writeByte(')');
14681457}
14691458
1459/// Prints a double quote, then escapes a string according to Zig string
1460/// literal rules, then a double quote.
1461pub fn printStringEscaped(w: *Writer, bytes: []const u8) Error!void {
1462 try w.writeByte('"');
1463 try std.zig.stringEscape(bytes, w);
1464 try w.writeByte('"');
1465}
1466
14701467pub fn printVector(
14711468 w: *Writer,
14721469 comptime fmt: []const u8,
......@@ -2121,6 +2118,11 @@ test "printFloat with comptime_float" {
21212118 try testing.expectFmt("1", "{}", .{1.0});
21222119}
21232120
2121test "{q} format string" {
2122 const data: []const u8 = "i\tlike\"cheese\x00\x05cheese";
2123 try testing.expectFmt("hello \"i\\tlike\\\"cheese\\x00\\x05cheese\" world", "hello {q} world", .{data});
2124}
2125
21242126fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void {
21252127 var buffer: [100]u8 = undefined;
21262128 var w: Writer = .fixed(&buffer);