Skip to content

char_sink.hpp

ICharSink — where formatted diagnostic BYTES physically go (a terminal, a captured string in a test, later a serial port).

This header declares 1 type (7 members).

Extracted from include/shulib/hal/char_sink.hpp — this page is that header's documentation, reformatted, so it cannot disagree with the code. Prose about how to think about the API lives in the user guide; worked recipes live in the cookbook; this page is the complete, mechanical list of what exists.

Contents

class ICharSink

class ICharSink

Where formatted diagnostic BYTES physically go — a terminal, a captured string in a test, later a serial port. Injecting the character device is what makes TermSink's output a testable claim: a host test asserts its exact bytes against a golden string, which hard-coding std::cout would have made impossible. Sanitization is the FORMATTER's job, not a sink's; a sink takes the bytes verbatim. NOT one of the frozen ten runtime robot-HAL interfaces — this is an additive diagnostics-output seam.

class, declared at include/shulib/hal/char_sink.hpp:29.

ICharSink::~ICharSink

virtual ~ICharSink() = default

Polymorphic-base plumbing: the destructor is virtual so a sink may be owned and destroyed through an ICharSink*, and declaring it suppresses the implicit copy/move, which are re-defaulted here. Defaulted rather than deleted because this seam holds no state — but the concrete sinks DO (a FILE* on the robot, a captured std::string in tests), so copying THROUGH this base slices one down to the empty interface. Nothing in the library owns a sink: TermSink keeps an ICharSink&, so the sink must outlive every TermSink pointed at it, and opening/closing the device stays the caller's job.

function, declared at include/shulib/hal/char_sink.hpp:39.

ICharSink::ICharSink

ICharSink() = default

Covered by the comment on ~ICharSink — one comment documents this run of special members.

function, declared at include/shulib/hal/char_sink.hpp:40.

ICharSink::ICharSink (overload 2)

ICharSink(const ICharSink&) = default

Covered by the comment on ~ICharSink — one comment documents this run of special members.

function, declared at include/shulib/hal/char_sink.hpp:41.

ICharSink::ICharSink (overload 3)

ICharSink(ICharSink&&) = default

Covered by the comment on ~ICharSink — one comment documents this run of special members.

function, declared at include/shulib/hal/char_sink.hpp:42.

ICharSink::operator=

ICharSink& operator=(const ICharSink&) = default

Covered by the comment on ~ICharSink — one comment documents this run of special members.

function, declared at include/shulib/hal/char_sink.hpp:43.

ICharSink::operator= (overload 2)

ICharSink& operator=(ICharSink&&) = default

Covered by the comment on ~ICharSink — one comment documents this run of special members.

function, declared at include/shulib/hal/char_sink.hpp:44.

ICharSink::write

virtual void write(std::string_view text) = 0

Write text verbatim to the device. MUST NOT throw.

function, declared at include/shulib/hal/char_sink.hpp:47.

Design commentary, from the header

The header opens with the reasoning behind these shapes. It is reproduced here in full because a reference that only lists signatures teaches nobody why.

The header’s own reasoning — 15 lines
 ICharSink — where formatted diagnostic BYTES physically go (a terminal, a captured
 string in a test, later a serial port). It exists so TermSink's output is a TESTABLE
 claim: with the character device injected (mirroring the injected-clock pattern in
 control/), a host test asserts TermSink's exact bytes against a golden string —
 hard-coding std::cout would have made "readable, column-aligned" unfalsifiable.

 NOT part of the frozen F4 ten (that freeze covers the 10 runtime robot-HAL
 interfaces); this is an ADDITIVE diagnostics-output seam introduced at chunk A1.
 R1 adds the on-robot stdout adapter; test/ uses hal::fake::FakeCharSink.

 Contract: write() takes the bytes verbatim (the FORMATTER owns sanitization — see
 TermSink), is called synchronously on the caller's task, and MUST NOT throw. One
 write() call carries one complete line, so an implementation that is atomic per call
 never interleaves lines.

Last updated 2026-08-15 — published from the main release branch, commit fa545ff. This site tracks releases, so between them it can lag the repository — that lag is deliberate, and it is what keeps the development log off the public site. For the current state of the code, see the repository.