#Format
Status: standard library specification draft.
This chapter defines the part of std.format that lib/std writes in
ordinary hd over the language tier:
- the text that the prelude function
debugreturns; - the
DebugWriterbuilders and their layout; - the builder calls that
@derive(Debug)generates; - which public
stdtypes implementDebug.
The language tier keeps what the compiler knows by name (Debug Trait):
| Item | Why it stays in the language tier |
|---|---|
Display, Debug |
prelude traits; assert_equal requires Debug (module.testing.assert-equal-debug) |
debug's signature |
a prelude function (module.prelude.debug) |
DebugWriter |
Debug's one method names it in its signature |
@derive(Debug) |
a derivation through a template |
dbg |
a prelude function with a compiler-supplied body (module.prelude.dbg), which prints through Debug where a type implements it |
#Debug Text
- std-format.debug.renderThe prelude function
debug(value)returns the text thatDebugwrites forvalue: stable, field by field, multi-line, and consistently indented. - std-format.debug.render.whole
debug(value)applies none of the limits of Large Values, which onlydbgapplies, so it returns the whole text. - std-format.debug.source
Debugwrites a value the way hd source writes it. So a printed value pastes back as hd code and builds an equal value, wherever each of its parts has a source form. - std-format.debug.source.no-contextThe text needs no expected type: a variant is written qualified by its enum, never as the
.Variantshorthand. - std-format.debug.source.markersA part with no source form writes a marker instead. It writes
<fn ...>for a function,<handle>for a runtime handle, and<cycle>and the…limits ofdbgfor a large value. Such text is not source.
| Rule | Value | Text |
|---|---|---|
| std-format.debug.source.dataStruct | a struct value | Point { x: 1, y: 2 }, and Empty {} without fields |
| std-format.debug.source.variantEnum variant | a variant of an enum | the qualified variant, then a call's arguments: positional payloads first, then named ones as name=value, as in Shape.Dot, Shape.Pair(1, 2), Shape.Circle(radius=2.0), and Shape.Mixed(1, label="x") |
| std-format.debug.source.preludeOption and Result | T? and Result[T, E] |
Option.Some(1), Option.None, Result.Ok(1), and Result.Err("bad"), as data.prelude.values spells them |
| std-format.debug.source.newtypeNewtype | a newtype value | its constructor call, as in Meters(2.5), unless it derives Debug, which writes the base value as trait.derive.newtype.templated says |
| std-format.debug.source.tupleTuple | a tuple | its elements in parentheses, as in (1, "a"), (1,), and () |
| std-format.debug.source.listList | a List[T] |
its elements in brackets, as in [1, 2], and [] when empty |
| std-format.debug.source.mapMap | a Map[K, V] |
its entries in braces, as in {"a": 1} |
| std-format.debug.source.stringString | a string |
a string literal that escapes \, ", and $ with a backslash, writes a line feed, carriage return, tab, and NUL as \n, \r, \t, and \0, and writes another control character as \u{HEX} |
| std-format.debug.source.charChar | a char |
a char literal that escapes \ and ' the same way and writes a control character as in a string, as in 'x' and '\n' |
enum Shape:
Dot
Circle(radius: f64)
fn shown() -> string:
debug(Shape.Circle(radius=2.0)) # "Shape.Circle(radius=2.0)", which is valid hd#Debug Builders
DebugWriter describes a value through builders, like Rust's Formatter:
use std.format.DebugWriter
struct Point:
x: i32
y: i32
impl Debug for Point:
fn debug(self, out: *mut DebugWriter) -> void:
out.debug_struct("Point").field("x", self.x).field("y", self.y).finish()| Rule | Call | Describes |
|---|---|---|
| std-format.debug.builder.structStruct | out.debug_struct(name), then .field(field_name, value) per field, then .finish() |
a named value with named fields |
| std-format.debug.builder.tupleTuple | out.debug_tuple(name), then .field(value) per field, then .finish() |
a named value with positional fields |
| std-format.debug.builder.named-fieldNamed argument | .named_field(arg_name, value) on a debug_tuple builder, after its field calls |
a named argument arg_name=value of the call that debug_tuple writes |
| std-format.debug.builder.listList | out.debug_list(), then .entry(value) per item, then .finish() |
a sequence |
| std-format.debug.builder.mapMap | out.debug_map(), then .entry(key, value) per pair, then .finish() |
key-value pairs |
| std-format.debug.builder.writeWrite | out.write(text) |
custom text, for a value that no builder fits |
- std-format.debug.writer-ref
DebugWriteris a struct with private fields, used behind*mut.writetakes&mut selfand the builder calls take*mut self, so each call writes to the one writer thatdebugreads when the value is done. - std-format.debug.builder.types-ref
std.formatdeclares the builder typesDebugStruct,DebugTuple,DebugList, andDebugMap, structs that each hold the*mut DebugWriterthey write to.debug_struct,debug_tuple,debug_list, anddebug_mapreturn a new builder object as a*mutreference,field,named_field, andentrytake*mut self, andfinishtakes&mut self. None is a prelude name. - std-format.debug.builder.valuesEach
fieldandentryvalue, and eachentrykey, must implementDebug. The builder writes it through its owndebug. - std-format.debug.builder.chain
fieldandentryreturn their builder, so calls chain, andfinishends the value. - std-format.debug.builder.tuple.unnamed
debug_tuple("")describes a tuple, so it writes no name. One field gets a comma after it, as in(1,), as a tuple'sDisplaytext does. - std-format.debug.layoutThe writer chooses a compact or a pretty layout. An implementation's builder calls are the same for both.
- std-format.debug.derive-builders
@derive(Debug)generates builder calls, as a hand-written implementation writes them, so derived and hand-written text share one layout. - std-format.debug.derive-builders.sourceThe derived calls write the value as hd source, by the shape of each value, as the table below states. A qualified variant name is the enum's name, a dot, and the variant's name.
| Rule | Value | Derived calls |
|---|---|---|
| std-format.debug.derive-builders.dataStruct | a struct value, with or without fields | out.debug_struct(type_name), then .field(field_name, value) per field in declaration order, then .finish() |
| std-format.debug.derive-builders.variant.namedNamed variant | a variant whose payload fields are all named | out.debug_tuple(qualified_name), then .named_field(field_name, value) per payload field in order, then .finish(), printing Shape.Circle(radius=2.0) |
| std-format.debug.derive-builders.variant.positionalPositional variant | a variant whose payload fields are all positional | out.debug_tuple(qualified_name), then .field(value) per payload value in order, then .finish() |
| std-format.debug.derive-builders.variant.unitUnit variant | a variant without a payload | out.write(qualified_name) only |
| std-format.debug.derive-builders.variant.mixedMixed variant | a variant with both positional and named payload fields, such as Mixed(i32, label: string) |
out.debug_tuple(qualified_name), then .field(value) per positional field, then .named_field(field_name, value) per named field, then .finish(), printing Mixed.Mixed(1, label="x") |
- std-format.debug.tuple-template
Debug's tuple template callsout.debug_tuple(""), then.field(value)per element, then.finish(). Its walker implementsrest, so a rest element's items are fields too.
Why.
assert_equaland property tests show failing values throughDebug, so any type a test compares can show itself without a user-facingDisplay. Builders keep the layout in the writer. Plain writes would fix it in each implementation, which leaves no pretty mode or depth limit and lets derived and hand-written text drift apart.
Note. The line breaks and indentation of
debugtext are standard-library API. The syntax of each value is specified bystd-format.debug.source, and fixtures assert only short values that fit on one line.
See also: Debug Trait, Standard Testing, Typed Derivation.
#Debug For Standard Types
The public types of std implement Debug, so a test can compare and
show them:
use std.time.{Duration, s}
fn shown(timeout: Duration) -> string:
debug(timeout)- std-format.debug.std-typesEvery public struct, enum, and newtype that
stddeclares implementsDebugwhen the type of each of its members implementsDebug. - std-format.debug.std-types.genericA generic one implements
Debugunder the bounds that its members need, as inimpl[T < Debug] Debug for Reverse[T]. - std-format.debug.std-types.callsIts builder calls are those that
@derive(Debug)generates for its declaration, bystd-format.debug.derive-builders.source. A newtype with@derive(Debug)writes its base value, astrait.derive.newtype.templatedgives. A std newtype with a hand-writtenDebug, such asPathorExitCode, writes its constructor call. - std-format.debug.std-types.sequences
DequeandHeapare exceptions tostd-format.debug.std-types.calls: each writes onedebug_listentry per element, asListdoes. ADequewrites them from front to back, and aHeapin an order this chapter does not specify. - std-format.debug.std-types.set
Setis one more exception: it writes onedebug_listentry per element, in iteration order. - std-format.debug.std-types.exempt-membersA type with a member that holds a function value, or a trait value whose trait does not extend
Debug, does not implementDebug. Examples areIterator[T]andChoices. - std-format.debug.std-types.time
Duration,Timestamp, andInstantare exceptions tostd-format.debug.std-types.calls. Each writes text withwrite. Sodebugshows aDurationas itsDisplaytext (250ms), aTimestampas its RFC 3339 text, and anInstantasInstant(5ms).
Note. The derived calls name a type's private fields too. Like all
debugtext, that text is not portable, and fixtures do not depend on it.
Why.
assert_equaland property tests show values throughDebug. A std type without it could not be compared in a test, andtrait.own.rulekeeps a caller from adding the implementation.
#References
A reference writes and displays as the value it refers to, as Rust's
&T does:
@derive(Debug)
enum Expr:
Num(n: i64)
Add(left: *Expr, right: *Expr)
fn shown(e: Expr, name: &string) -> string:
"${debug(e)} for $name" # Expr.Add(left=Expr.Num(n=1), right=Expr.Num(n=2)) for tea- std-format.debug.reference
std.formatimplementsDebugfor&Tand for&mut TwhenT < Debug. A reference writes exactly what the value it refers to writes, with no trace of the reference or its identity. - std-format.display.reference
std.formatimplementsDisplayfor&Tand for&mut TwhenT < Display. A reference's text is the text of the value it refers to, so"$r"interpolates*r. - std-format.debug.reference-derivedSo a derived
Debugwrites a reference field as the value it refers to, and a tree linked by references prints whole. - std-format.debug.reference-cycleA derived
Debugkeeps no record of the values it has written. On a structure whose references really form a cycle, it runs until the stack is exhausted, as a derivedEqdoes.
Why. Every tree type should print, and its links are part of its shape, not part of its value.
isandId[T]keep identity for code that needs it.