runnel 0.4.3

the pluggable io stream. now support: stdio, string io, in memory pipe, line pipe
Documentation
# Code Review: Runnel Library (v0.4.0)

## Overview
The `runnel` library is a well-structured, zero-dependency Rust crate that provides a pluggable I/O stream abstraction. It effectively decouples application logic from specific I/O implementations, making it particularly useful for testing and multi-threaded stream processing.

## 1. Architecture and Design

### 1.1. Core Traits and Abstraction
- **Strengths**: The separation of `StreamIn`, `StreamOut`, and `StreamErr` traits is clean and follows the Principle of Least Privilege. Each trait provides a focused interface.
- **Observation**: The use of `Box<dyn Trait>` allows for runtime polymorphism, which is the core of the "pluggable" nature.
- **Refinement Suggestion**: The `is_line_pipe()` method in the base traits feels like a slight violation of the Interface Segregation Principle or Liskov Substitution Principle. It's used to distinguish implementation details at the trait level. Consider if this could be handled via downcasting (using `Any`) or by providing specialized traits if line-based behavior is significantly different.

### 1.2. Builder Pattern
- **Strengths**: `RunnelIoeBuilder` provides a fluent and intuitive API for constructing the I/O container. The default behavior (falling back to stdio) makes it very easy to adopt.
- **Implementation**: The use of `Option<Box<dyn Stream...>>` in the builder is a standard and safe way to handle optional configuration.

## 2. Implementation Details

### 2.1. String I/O (`stringio.rs`)
- **Strengths**: Excellent for unit testing. The `RawStringIn` and `RawStringOut` implementations are straightforward.
- **Critical Issue**: The `lines()` implementation in `StringIn` (and similarly in `PipeIn` and `LinePipeIn`) uses `self.0.inner.lock().unwrap().take().unwrap()`.
    - **Impact**: This makes `lines()` a destructive operation. Calling `lines()` a second time on the same `StringIn` instance will result in a panic. 
    - **Recommendation**: Document this behavior clearly or refactor it to allow multiple calls (perhaps by returning a clone of the iterator state or ensuring only one iterator can exist but without panicking on the second call).

### 2.2. Pipe I/O (`pipeio.rs` & `linepipeio.rs`)
- **Strengths**: Leveraging `std::sync::mpsc::sync_channel` for thread-safe communication is idiomatic and performant. 
- **Auto-flush**: The auto-flush mechanism in `RawPipeOut` (using `BUF_SZ`) is a good performance optimization to reduce the number of channel messages.
- **LinePipe Optimization**: `linepipeio` is a clever optimization for line-based protocols, avoiding unnecessary byte-to-string conversions during transit.

### 2.3. Error Handling and Panics
- **Observation**: `mutex.lock().unwrap()` is used throughout the codebase. While common in some Rust projects where lock poisoning is considered unrecoverable, it can lead to abrupt crashes.
- **Recommendation**: Consider using `unwrap_or_else(|e| e.into_inner())` if you want to attempt recovery from a poisoned lock, or at least use `.expect("locking failed")` to provide more context if a panic occurs.

### 2.4. Performance and Inlining
- **Observation**: There is inconsistent use of `#[inline(always)]`. 
- **Recommendation**: Use `#[inline]` for small, frequently called methods (like wrappers in `stdio.rs`). `#[inline(always)]` should be used sparingly as it can sometimes hinder the compiler's own optimization heuristics.

## 3. Code Quality and Style

### 3.1. Idiomatic Rust
- The code generally follows Rust best practices.
- The use of `Box<dyn NextLine + '_>` is a good way to handle the lifetime of the line iterator.

### 3.2. Documentation and Examples
- **Strengths**: The crate-level documentation is excellent, providing clear examples for each major use case (stdio, stringio, pipeio, linepipeio).
- **Suggestion**: Adding a "Safety" or "Panics" section to the documentation for methods that can panic (like `lines()` mentioned above) would be beneficial for users.

## 4. Summary of Recommendations
1.  **Fix destructive `lines()`**: Address the `take().unwrap()` logic in `StringIn`, `PipeIn`, and `LinePipeIn` to either allow multiple calls or clearly document the panic.
2.  **Review `is_line_pipe()`**: Consider if this metadata belongs in the core trait or can be handled differently.
3.  **Harden Locking**: Move from `.unwrap()` to more descriptive error handling or intentional poisoning recovery for Mutexes.
4.  **Consistency**: Audit the use of `#[inline]` vs `#[inline(always)]`.

## Conclusion
The `runnel` library is a solid and useful tool for abstracting I/O in Rust. It's well-designed for its intended purpose. Addressing the minor robustness issues mentioned above would make it even more reliable for production use.

---
Review Date: 2026-05-15
Reviewer: Gemini CLI Agent