Why Is It Designed This Way?
Why separate Value from streams?
A value is repeatably observable and cloneable. A stream is consumable state. Putting streams inside cloneable values would require locks and make reading one alias change what another alias observes.
Why distinguish list streams from byte streams?
Structured commands need typed rows; external programs speak bytes and have stdin/stdout/exit semantics. One universal stream would either erase structure or pretend a child process is just an iterator.
Why use synchronous iterators?
Most pipeline stages do local CPU work and benefit from pull-based laziness. Iterators provide early stop and bounded memory with far less machinery than an async runtime, while blocking boundaries are handled separately.
Why pass EngineState and &mut Stack?
Global declarations/configuration are shared read-mostly; variables and environment changes are call-local mutations. The signature makes this ownership split visible to every command implementation.
Why is parallelism a separate par-each command?
Parallel closure evaluation can reorder output, amplify memory, and interact with side effects. Naming it lets users opt into those semantics and choose thread count and order preservation.
Why use a dedicated Rayon pool?
Other commands use Rayon’s global pool, and streaming parallel work may block on channels. Sharing workers can create starvation deadlocks. Nested parallel calls need a private pool for the same reason.
Why attach signals to streams?
Lazy computation happens during later consumption, far from the command that created it. Carrying the interrupt capability with the iterator ensures Ctrl-C still reaches the actual work.
Why wrap child processes in ByteStream?
Output bytes, stderr, exit status, foreground job control, and cleanup describe one lifecycle. Keeping them together prevents downstream commands from losing the process result while consuming its data.