# std.fmt - String Formatting and Parsing (Zig 0.16.0) String formatting and parsing utilities: format strings, integer/float parsing, hex encoding/decoding, and custom formatters. Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html ## Zig 0.16 Formatting Notes - `std.fmt.format` is replaced by `std.Io.Writer.print`. - `std.fmt.Formatter` is renamed to `std.fmt.Alt`. - `std.fmt.FormatOptions` is renamed to `std.fmt.Options`. - `std.fmt.bufPrintZ` remains as a deprecated compatibility wrapper; use `std.fmt.bufPrintSentinel`. - The `{D}` duration specifier was removed; format `std.Io.Duration` with `{f}`. ```zig try writer.print("{f}", .{std.Io.Duration.fromMilliseconds(250)}); ``` Custom formatter signature remains writer-centered: ```zig pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { try writer.print("{s}", .{self.name}); } ``` ## Table of Contents - [Format String Syntax](#format-string-syntax) - [Format Specifiers](#format-specifiers) - [Integer Parsing](#integer-parsing) - [Float Parsing](#float-parsing) - [Hex Encoding/Decoding](#hex-encodingdecoding) - [Buffer Printing](#buffer-printing) - [Allocating Print](#allocating-print) - [Comptime Print](#comptime-print) - [Custom Formatters](#custom-formatters) - [Format String Parser](#format-string-parser) ## Format String Syntax Full syntax: `{[argument][specifier]:[fill][alignment][width].[precision]}` ### Components | Component | Description | Example | |-----------|-------------|---------| | `argument` | Argument index or bracketed name | `{0}`, `{[name]s}` | | `fill` | Padding character | `{:0>5}` uses `0` | | `alignment` | `<` left, `^` center, `>` right | `{:<10}` | | `width` | Minimum field width | `{:10}` | | `precision` | Decimal places for floats | `{:.2}` | | `specifier` | Output format | `{d}`, `{x}`, `{s}` | ### Examples ```zig std.debug.print("{d:0>8}\n", .{42}); // "00000042" std.debug.print("{s:_^10}\n", .{"hi"}); // "____hi____" std.debug.print("{d:.2}\n", .{3.14159}); // "3.14" std.debug.print("{0} {1} {0}\n", .{"a", "b"}); // "a b a" ``` ### Named Arguments ```zig std.debug.print("{[name]s}: {[value]d}\n", .{ .name = "x", .value = 42 }); ``` ### Runtime Width/Precision ```zig std.debug.print("{[value]d:[width]}\n", .{ .value = 42, .width = @as(usize, 8) }); std.debug.print("{[value]d:.[prec]}\n", .{ .value = 3.14159, .prec = @as(usize, 2) }); ``` ### Escape Braces ```zig std.debug.print("{{literal}}\n", .{}); // "{literal}" ``` ## Format Specifiers ### Selected Type Specifiers This table lists common specifiers; it is not a complete substitute for `std.Io.Writer`'s type-specific formatting rules. | Specifier | Types | Output | |-----------|-------|--------| | `{}` | any | Default formatting | | `{d}` | int, float, enum | Decimal | | `{b}` | int, enum | Binary | | `{o}` | int, enum | Octal | | `{x}` | int, float, `[]u8`, enum | Lowercase hex | | `{X}` | int, float, `[]u8`, enum | Uppercase hex | | `{s}` | `[]const u8`, `[*:0]const u8` | String | | `{c}` | u8 | ASCII character | | `{u}` | u21 | UTF-8 codepoint | | `{e}` | float | Scientific notation | | `{f}` | has `format` method | Custom formatter | | `{*}` | pointer | Address (`Type@0x...`) | | `{?}` | optional | Value or `null` | | `{!}` | error union | Value or `error.Name` | | `{any}` | any | Debug representation | ### Integer Examples ```zig std.debug.print("{d}\n", .{255}); // "255" std.debug.print("{x}\n", .{255}); // "ff" std.debug.print("{X}\n", .{255}); // "FF" std.debug.print("{b}\n", .{5}); // "101" std.debug.print("{o}\n", .{64}); // "100" std.debug.print("{c}\n", .{'A'}); // "A" std.debug.print("{u}\n", .{0x1F310}); // globe emoji ``` ### Float Examples ```zig std.debug.print("{d}\n", .{3.14159}); // "3.14159" std.debug.print("{d:.2}\n", .{3.14159}); // "3.14" std.debug.print("{e}\n", .{1234.5}); // "1.2345e3" std.debug.print("{e:.3}\n", .{1234.5}); // "1.234e3" std.debug.print("{x}\n", .{@as(f32, 1.0)}); // "0x1p0" std.debug.print("{x:.5}\n", .{@as(f32, 1.0)}); // "0x1.00000p0" ``` ### Special Float Values ```zig std.debug.print("{}\n", .{std.math.nan(f64)}); // "nan" std.debug.print("{}\n", .{std.math.inf(f64)}); // "inf" std.debug.print("{}\n", .{-std.math.inf(f64)}); // "-inf" ``` ### Slice/Array Formatting ```zig const bytes: []const u8 = "hello"; std.debug.print("{s}\n", .{bytes}); // "hello" std.debug.print("{x}\n", .{bytes}); // "68656c6c6f" std.debug.print("{any}\n", .{bytes}); // "{ 104, 101, 108, 108, 111 }" ``` ### Padding and Alignment ```zig std.debug.print("{d:5}\n", .{42}); // " 42" (right, default) std.debug.print("{d:<5}\n", .{42}); // "42 " (left) std.debug.print("{d:^5}\n", .{42}); // " 42 " (center) std.debug.print("{d:0>5}\n", .{42}); // "00042" (zero-pad) std.debug.print("{d:=>5}\n", .{42}); // "===42" (custom fill) ``` ## Integer Parsing ### parseInt Parse signed or unsigned integers with optional base detection. ```zig const std = @import("std"); // Explicit base const a = try std.fmt.parseInt(i32, "-123", 10); // -123 const b = try std.fmt.parseInt(u32, "ff", 16); // 255 const c = try std.fmt.parseInt(u8, "101", 2); // 5 // Auto-detect base (base = 0) const d = try std.fmt.parseInt(i32, "0x1f", 0); // 31 (hex) const e = try std.fmt.parseInt(i32, "0b101", 0); // 5 (binary) const f = try std.fmt.parseInt(i32, "0o17", 0); // 15 (octal) const g = try std.fmt.parseInt(i32, "42", 0); // 42 (decimal) // Underscores are ignored within the digit sequence. Leading/trailing // underscores and underscores immediately after a base prefix are invalid. const h = try std.fmt.parseInt(u32, "1_000_000", 10); // 1000000 const i = try std.fmt.parseInt(u32, "0xff_ff", 0); // 65535 ``` **Errors:** - `error.InvalidCharacter` - Invalid digit for base, leading/trailing underscore, empty string - `error.Overflow` - Result doesn't fit in type ### parseUnsigned Parse unsigned integers only (rejects `+` and `-` signs). ```zig const a = try std.fmt.parseUnsigned(u16, "65535", 10); // 65535 const b = try std.fmt.parseUnsigned(u8, "ff", 16); // 255 // These return error.InvalidCharacter: // std.fmt.parseUnsigned(u8, "+10", 10) // std.fmt.parseUnsigned(u8, "-10", 10) ``` ### parseIntSizeSuffix Parse integers with SI size suffixes (K, M, G, T, P, E, Z, Y, R, Q). ```zig const std = @import("std"); const a = try std.fmt.parseIntSizeSuffix("2", 10); // 2 const b = try std.fmt.parseIntSizeSuffix("2B", 10); // 2 const c = try std.fmt.parseIntSizeSuffix("2k", 10); // 2000 const d = try std.fmt.parseIntSizeSuffix("2kB", 10); // 2000 const e = try std.fmt.parseIntSizeSuffix("2Ki", 10); // 2048 (binary) const f = try std.fmt.parseIntSizeSuffix("2KiB", 10); // 2048 (binary) const g = try std.fmt.parseIntSizeSuffix("1M", 10); // 1000000 const h = try std.fmt.parseIntSizeSuffix("1Mi", 10); // 1048576 const i = try std.fmt.parseIntSizeSuffix("aKiB", 16); // 10240 (hex base) ``` ### charToDigit / digitToChar Convert between characters and digit values. ```zig const d = try std.fmt.charToDigit('a', 16); // 10 const c = std.fmt.digitToChar(10, .lower); // 'a' const C = std.fmt.digitToChar(10, .upper); // 'A' ``` ## Float Parsing ### parseFloat Parse floating-point numbers from strings. ```zig const std = @import("std"); // Decimal notation const a = try std.fmt.parseFloat(f64, "3.14159"); // 3.14159 const b = try std.fmt.parseFloat(f32, "-123.456"); // -123.456 const c = try std.fmt.parseFloat(f64, "1e10"); // 1e10 const d = try std.fmt.parseFloat(f64, "1.5e-3"); // 0.0015 const e = try std.fmt.parseFloat(f64, "+0"); // 0.0 const f = try std.fmt.parseFloat(f64, "-0"); // -0.0 // Hexadecimal notation const g = try std.fmt.parseFloat(f64, "0x1p0"); // 1.0 const h = try std.fmt.parseFloat(f64, "0x1.8p1"); // 3.0 const i = try std.fmt.parseFloat(f32, "-0x1p-1"); // -0.5 // Special values const nan = try std.fmt.parseFloat(f64, "nan"); // NaN const inf = try std.fmt.parseFloat(f64, "inf"); // +Inf const ninf = try std.fmt.parseFloat(f64, "-inf"); // -Inf // Underscores allowed between digits const j = try std.fmt.parseFloat(f64, "1_234.567_8"); // 1234.5678 ``` **Supported types:** `f16`, `f32`, `f64`, `f80`, `f128` **Errors:** - `error.InvalidCharacter` - Invalid format, empty string, invalid underscore placement ## Hex Encoding/Decoding ### bytesToHex Convert bytes to hexadecimal string. ```zig const input = "hello"; const hex_lower = std.fmt.bytesToHex(input, .lower); // "68656c6c6f" const hex_upper = std.fmt.bytesToHex(input, .upper); // "68656C6C6F" ``` ### hexToBytes Decode hexadecimal string to bytes. ```zig var buf: [32]u8 = undefined; const decoded = try std.fmt.hexToBytes(&buf, "48656c6c6f"); // "Hello" ``` **Errors:** - `error.InvalidCharacter` - Non-hex character - `error.InvalidLength` - Odd number of hex digits - `error.NoSpaceLeft` - Output buffer too small ### hex Convert unsigned integer to little-endian hex bytes. ```zig const h = std.fmt.hex(@as(u32, 0xdeadbeef)); // "efbeadde" ``` ## Buffer Printing ### bufPrint Format into a fixed buffer, returns slice of written data. ```zig var buf: [256]u8 = undefined; const result = try std.fmt.bufPrint(&buf, "Hello {s}!", .{"world"}); // result = "Hello world!" ``` **Errors:** - `error.NoSpaceLeft` - Buffer too small ### bufPrintSentinel Format into buffer with null terminator. ```zig var buf: [256]u8 = undefined; const result = try std.fmt.bufPrintSentinel(&buf, "Hello {s}!", .{"world"}, 0); // result is [:0]u8 = "Hello world!" (null-terminated) ``` ### count Count output bytes needed for the format (without allocating). ```zig const len = std.fmt.count("Value: {d}, Name: {s}", .{ 42, "test" }); // len = 21 ``` ## Allocating Print ### allocPrint Format with dynamic allocation. ```zig const allocator = std.heap.page_allocator; const result = try std.fmt.allocPrint(allocator, "Hello {s}!", .{"world"}); defer allocator.free(result); // result = "Hello world!" ``` ### allocPrintSentinel Format with allocation and sentinel terminator. ```zig const result = try std.fmt.allocPrintSentinel(allocator, "Hello {s}", .{"world"}, 0); defer allocator.free(result); // result is [:0]u8 = "Hello world" (null-terminated) ``` ## Comptime Print ### comptimePrint Format at compile time, returns pointer to comptime-known string. ```zig const msg = comptime std.fmt.comptimePrint("Value: {d}", .{100}); // msg: *const [10:0]u8 = "Value: 100" ``` ## Custom Formatters ### Using `{f}` Specifier Types with a `format` method use `{f}`: ```zig const Point = struct { x: f32, y: f32, pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { try writer.print("({d:.2}, {d:.2})", .{ self.x, self.y }); } }; const p = Point{ .x = 1.5, .y = 2.5 }; std.debug.print("{f}\n", .{p}); // "(1.50, 2.50)" ``` ### Alt (Formatter Wrapper) Create a type that wraps data with a custom format function. ```zig const std = @import("std"); fn formatReversed(data: []const u8, writer: *std.Io.Writer) std.Io.Writer.Error!void { var i = data.len; while (i > 0) { i -= 1; try writer.writeByte(data[i]); } } const Reversed = std.fmt.Alt([]const u8, formatReversed); pub fn main() !void { const rev = Reversed{ .data = "hello" }; std.debug.print("{f}\n", .{rev}); // "olleh" } ``` ### alt Helper Call alternate format methods by name. ```zig const Example = struct { number: u8, pub fn asHex(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { try writer.print("0x{x:0>2}", .{self.number}); } }; const ex = Example{ .number = 42 }; std.debug.print("{f}\n", .{std.fmt.alt(ex, .asHex)}); // "0x2a" ``` ## Format String Parser For implementing custom formatters compatible with std.fmt. ### Parser Stream-based parser for format strings. ```zig const std = @import("std"); var parser: std.fmt.Parser = .{ .bytes = "hello:world", .i = 0 }; // Parse until delimiter const before = parser.until(':'); // "hello" // Consume delimiter _ = parser.char(); // ':' // Check for character if (parser.maybe('w')) { // consumed 'w' } // Parse number parser = .{ .bytes = "42abc", .i = 0 }; const num = parser.number(); // 42 // Peek without consuming const next = parser.peek(0); // 'a' ``` ### Placeholder Parse format placeholder syntax. ```zig const ph = std.fmt.Placeholder.parse("0d:0>8.2"); // ph.arg = .{ .number = 0 } // ph.specifier_arg = "d" // ph.fill = '0' // ph.alignment = .right // ph.width = .{ .number = 8 } // ph.precision = .{ .number = 2 } ``` ### Specifier Argument reference in format string. ```zig const Specifier = union(enum) { none, // {} - auto-increment number: usize, // {0} - positional named: []const u8, // {name} - named }; ``` ## Utility Functions ### digits2 Fast conversion of 0-99 to two-digit string. ```zig const d = std.fmt.digits2(42); // "42" const z = std.fmt.digits2(7); // "07" ``` ### printInt Print integer to buffer, returns end index. ```zig var buf: [32]u8 = undefined; const end = std.fmt.printInt(&buf, @as(i32, -42), 10, .lower, .{}); const result = buf[0..end]; // "-42" ``` ## Types ### Options Formatting options for numbers. ```zig const Options = struct { precision: ?usize = null, width: ?usize = null, alignment: Alignment = .right, fill: u8 = ' ', }; ``` ### Number Extended options for numeric formatting. ```zig const Number = struct { mode: Mode = .decimal, // .decimal, .binary, .octal, .hex, .scientific case: Case = .lower, // .lower, .upper precision: ?usize = null, width: ?usize = null, alignment: Alignment = .right, fill: u8 = ' ', }; ``` ### Alignment ```zig const Alignment = enum { left, center, right }; ``` ### Case ```zig const Case = enum { lower, upper }; ``` ## Error Types ```zig const ParseIntError = error{ Overflow, InvalidCharacter }; const ParseFloatError = error{ InvalidCharacter }; const BufPrintError = error{ NoSpaceLeft }; ``` ## Constants ```zig const default_max_depth = 3; // Default recursion depth for {any} const hex_charset = "0123456789abcdef"; ```