One debug path: build-gated, no flags
ral has a single debug-tracing primitive, dbg_trace!, gated on
debug_assertions alone — tracing is a property of how the binary was
compiled, never of the runtime environment.
dbg_trace!(tag, …)(in diagnostics) emits a tagged red line to stderr in debug builds and expands to nothing in release. Its call sites are permanent instrumentation — kept in the source, not added and removed per investigation.- The same
debug_assertionsgate governs thetry-caught-error echo (control-operators,eval_try): debug builds echoral: try caught error …to stderr; release builds stay silent (docs/SPEC.md§10.1).
Why one path. A second, environment-gated channel earns nothing: a developer who wants traces builds in debug, and a release user wants silence and gets it unconditionally. A single build-time gate removes the question of which switch is live — there is no switch.
Consequence — drain stderr concurrently. Because a debug build traces
unconditionally, any consumer of a debug ral child’s stderr must drain it
while waiting on the child: the per-child wait/drain traces can outrun a
pipe buffer and block the writer. The runtime drains its pump streams on
their own threads; a harness spawning ral does the same — run_with_timeout
in ral/tests/pipeline.rs reads both pipes on reader threads rather than
after the child exits.