hd-lang

#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 debug returns;
  • the DebugWriter builders and their layout;
  • the builder calls that @derive(Debug) generates;
  • which public std types implement Debug.

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

  1. std-format.debug.renderThe prelude function debug(value) returns the text that Debug writes for value: stable, field by field, multi-line, and consistently indented.
  2. std-format.debug.render.wholedebug(value) applies none of the limits of Large Values, which only dbg applies, so it returns the whole text.
  3. std-format.debug.sourceDebug writes 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.
  4. std-format.debug.source.no-contextThe text needs no expected type: a variant is written qualified by its enum, never as the .Variant shorthand.
  5. 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 of dbg for 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
  1. std-format.debug.writer-refDebugWriter is a struct with private fields, used behind *mut. write takes &mut self and the builder calls take *mut self, so each call writes to the one writer that debug reads when the value is done.
  2. std-format.debug.builder.types-refstd.format declares the builder types DebugStruct, DebugTuple, DebugList, and DebugMap, structs that each hold the *mut DebugWriter they write to. debug_struct, debug_tuple, debug_list, and debug_map return a new builder object as a *mut reference, field, named_field, and entry take *mut self, and finish takes &mut self. None is a prelude name.
  3. std-format.debug.builder.valuesEach field and entry value, and each entry key, must implement Debug. The builder writes it through its own debug.
  4. std-format.debug.builder.chainfield and entry return their builder, so calls chain, and finish ends the value.
  5. std-format.debug.builder.tuple.unnameddebug_tuple("") describes a tuple, so it writes no name. One field gets a comma after it, as in (1,), as a tuple's Display text does.
  6. std-format.debug.layoutThe writer chooses a compact or a pretty layout. An implementation's builder calls are the same for both.
  7. 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.
  8. 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")
  1. std-format.debug.tuple-templateDebug's tuple template calls out.debug_tuple(""), then .field(value) per element, then .finish(). Its walker implements rest, so a rest element's items are fields too.

Why. assert_equal and property tests show failing values through Debug, so any type a test compares can show itself without a user-facing Display. 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 debug text are standard-library API. The syntax of each value is specified by std-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)
  1. std-format.debug.std-typesEvery public struct, enum, and newtype that std declares implements Debug when the type of each of its members implements Debug.
  2. std-format.debug.std-types.genericA generic one implements Debug under the bounds that its members need, as in impl[T < Debug] Debug for Reverse[T].
  3. std-format.debug.std-types.callsIts builder calls are those that @derive(Debug) generates for its declaration, by std-format.debug.derive-builders.source. A newtype with @derive(Debug) writes its base value, as trait.derive.newtype.templated gives. A std newtype with a hand-written Debug, such as Path or ExitCode, writes its constructor call.
  4. std-format.debug.std-types.sequencesDeque and Heap are exceptions to std-format.debug.std-types.calls: each writes one debug_list entry per element, as List does. A Deque writes them from front to back, and a Heap in an order this chapter does not specify.
  5. std-format.debug.std-types.setSet is one more exception: it writes one debug_list entry per element, in iteration order.
  6. 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 implement Debug. Examples are Iterator[T] and Choices.
  7. std-format.debug.std-types.timeDuration, Timestamp, and Instant are exceptions to std-format.debug.std-types.calls. Each writes text with write. So debug shows a Duration as its Display text (250ms), a Timestamp as its RFC 3339 text, and an Instant as Instant(5ms).

Note. The derived calls name a type's private fields too. Like all debug text, that text is not portable, and fixtures do not depend on it.

Why. assert_equal and property tests show values through Debug. A std type without it could not be compared in a test, and trait.own.rule keeps 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
  1. std-format.debug.referencestd.format implements Debug for &T and for &mut T when T < Debug. A reference writes exactly what the value it refers to writes, with no trace of the reference or its identity.
  2. std-format.display.referencestd.format implements Display for &T and for &mut T when T < Display. A reference's text is the text of the value it refers to, so "$r" interpolates *r.
  3. std-format.debug.reference-derivedSo a derived Debug writes a reference field as the value it refers to, and a tree linked by references prints whole.
  4. std-format.debug.reference-cycleA derived Debug keeps 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 derived Eq does.

Why. Every tree type should print, and its links are part of its shape, not part of its value. is and Id[T] keep identity for code that needs it.