Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

std.report

std.report provides bounded human-readable reports using the same std.fmt.Formatter[W] and FormatError as value display and f-strings. It provides rendering helpers for the canonical std.fmt.Formattable protocol; it does not own another formatter, protocol, or error family. Import formatter types and constructors from std.fmt.

import std.report
from std.fmt import FormatError, Formatter

pub class DiskFull:
    path: str
    needed: u64

    pub def format[W](self, output: &mut Formatter[W]) -> Result[(), FormatError]:
        output.text("disk full writing ")?
        output.text(&self.path)?
        output.text(": need ")?
        output.unsigned(self.needed, 10)?
        output.text(" more bytes")

def main() -> Result[(), IoError | FormatError]:
    error = DiskFull("out.csv", 4096u64)
    print(std.report.to_string(&error, 256, 4)?)?
    Ok(())
disk full writing out.csv: need 4096 more bytes

Formatting contract

The structural Formattable protocol requires a public shared-receiver method on a public class or enum, since its bound is checked in std.fmt:

pub def format[W](self, output: &mut Formatter[W]) -> Result[(), FormatError]:
    pass

The writer parameter belongs to the method. A single implementation can render to a string, stderr, or a caller-owned writer, and can call all the scalar and format-spec methods described in std.fmt. For example, output.float_spec(value, &std.fmt.spec(precision=2, kind="f")) is available inside a report. Scratch for advanced numbers can allocate; this does not alter the allocation-free text/integer/boolean stderr path.

render[T: Formattable, W](&T, &mut Formatter[W]) borrows the value, enters one depth level, calls format, and restores depth even on error. Depth is in 0..=64; zero permits direct scalar writes but rejects render. Nested values should use render to participate in the depth limit. Direct user-method calls do not count depth automatically.

An anonymous sum satisfies Formattable when every normalized member satisfies it. Dispatch borrows the active member and calls its writer-generic format method without moving the payload or allocating a wrapper. The sum adds no nesting level beyond the one entered by render. Aliases, reordered members and singleton normalization preserve this behavior. Direct sum.format(&mut output) uses the same dispatch without entering a depth level. A missing, private, or mismatched method on any member fails the bound, even when that member is inactive. This forwarding is specific to std.fmt.Formattable; other protocols, including display/debug, are not automatically forwarded.

Helpers and destinations

OperationBehavior
std.report.render(&value, &mut output)Borrowed report into any Formatter[W]
std.report.to_string(&value, max_bytes: NonNegative[i64], max_depth: NonNegative[i64])Create a string formatter, render, and finish
std.fmt.formatter(writer, scratch, max_bytes, max_depth)Transfer a writer and caller-provided scratch into a formatter
std.fmt.string_formatter(max_bytes, max_depth)Create an allocating Formatter[Cursor]
std.fmt.stderr_formatter(max_bytes, max_depth)Create an allocation-free Formatter[Stderr]
std.fmt.finish(&output)Copy a string formatter into an owned str; takes &Formatter[Cursor]

The formatter does not add punctuation, flush stdout, close a stream, or lock a whole record. Each write counts toward its byte budget. completed() records the accepted prefix. has_failed() records terminal stderr failure; subsequent emissions on that formatter return FormatError.OutputClosed without writing. Other writer destinations retain their own recovery policy and do not set this flag. Formatting limits and allocation failures leave the formatter usable. See errors and partial output for shared errors and retry rules. In particular, finishing stderr is rejected by its type.

For allocation-free stderr reports, use text, integer, unsigned, and boolean. Text writes use the native descriptor-2 operation directly, retry interruptions, and count successful short writes. A closed or read-only stderr returns Output(StreamError.Io(IoError.System(9))) (EBADF). On supported x86-64 Linux the write suppresses SIGPIPE for that operation, so a closed pipe returns an error and normal cleanup runs. Advanced formatting lazily acquires scratch and can fail with Allocation(...).

The string formatter stages a complete UTF-8 text emission before calling its cursor. Failed staging or cursor growth preserves the valid UTF-8 output and byte count. finish borrows the formatter, so an allocation failure can be retried. A whole record can still have a successful prefix before a later emission fails; do not replay it blindly.

Current boundaries

This protocol selects human-readable entrypoint reports; types without it retain structural rendering. std.error.Context[E] supports the protocol when its cause does. display/debug remain the value presentation hooks; sharing the formatter does not change which hook an entrypoint chooses. The tutorial shows enum and sum reporting.

The completed reporting proposal records the original reporting design and its later unification.