zig-skills/references/std-fmt.md

14 KiB

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}.
try writer.print("{f}", .{std.Io.Duration.fromMilliseconds(250)});

Custom formatter signature remains writer-centered:

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

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

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

std.debug.print("{[name]s}: {[value]d}\n", .{ .name = "x", .value = 42 });

Runtime Width/Precision

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

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

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

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

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

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

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.

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).

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).

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.

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.

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.

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.

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.

const h = std.fmt.hex(@as(u32, 0xdeadbeef));  // "efbeadde"

Buffer Printing

bufPrint

Format into a fixed buffer, returns slice of written data.

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.

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).

const len = std.fmt.count("Value: {d}, Name: {s}", .{ 42, "test" });
// len = 21

Allocating Print

allocPrint

Format with dynamic allocation.

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.

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.

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}:

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.

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.

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.

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.

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.

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.

const d = std.fmt.digits2(42);  // "42"
const z = std.fmt.digits2(7);   // "07"

printInt

Print integer to buffer, returns end index.

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.

const Options = struct {
    precision: ?usize = null,
    width: ?usize = null,
    alignment: Alignment = .right,
    fill: u8 = ' ',
};

Number

Extended options for numeric formatting.

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

const Alignment = enum { left, center, right };

Case

const Case = enum { lower, upper };

Error Types

const ParseIntError = error{ Overflow, InvalidCharacter };
const ParseFloatError = error{ InvalidCharacter };
const BufPrintError = error{ NoSpaceLeft };

Constants

const default_max_depth = 3;        // Default recursion depth for {any}
const hex_charset = "0123456789abcdef";