Box<dyn Read> provides flexibility, but it comes with inherent costs:
- Virtual dispatch on every read call
- A heap allocation to store the trait object
- An additional level of indirection
This can introduce avoidable overhead, particularly when working with in-memory sources or buffered input where the cost of I/O is not dominant.
In many utilities, the set of input sources is limited and known in advance (e.g., Stdin and File). In such cases, type erasure via Box<dyn Read> is not strictly necessary. A concrete enum can eliminate both heap allocation and dynamic dispatch.
A pattern already used elsewhere in the codebase (e.g., comm.rs) demonstrates this approach:
enum Input {
Stdin(std::io::Stdin),
File(std::fs::File),
}
impl Read for Input {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self {
Input::Stdin(s) => s.read(buf),
Input::File(f) => f.read(buf),
}
}
}
Box<dyn Read>provides flexibility, but it comes with inherent costs:This can introduce avoidable overhead, particularly when working with in-memory sources or buffered input where the cost of I/O is not dominant.
In many utilities, the set of input sources is limited and known in advance (e.g.,
StdinandFile). In such cases, type erasure viaBox<dyn Read>is not strictly necessary. A concrete enum can eliminate both heap allocation and dynamic dispatch.A pattern already used elsewhere in the codebase (e.g.,
comm.rs) demonstrates this approach: