While using csv crate, I've found myself wanting to do this:
let file: Option<fs::File> = None;
let rdr = match condition {
Some(file) => csv::Reader::from_reader(file)
None => csv::Reader::from_reader(io::stdin())
}
for record in rdr.records() {
// Do whatever
}
However, the csv::Reader type has a generic, so in the first branch it is csv::Reader<fs::File>, and in the second it is csv::Reader<io::Stdin>. This fact prohibits me from compiling this program, even though I don't really care about the exact type used inside the csv::Reader, as all methods are implemented over the interface.
impl<R: io::Read> Reader<R> {
I understand that there's a difference in memory between them, and that's why I cannot do that without an enum. Although, it would arguably be a good zero-cost abstraction, if it just wrapped it in an enum implicitly and let me match on interface when I need to.
But the question rather is — how do I do something similar, that would actually work.
For enum-based solutions, in this case you could use the Read impl of Either, so something like this should work:
// using crate `either`
use either::Either;
use Either::{Left, Right};
let file: Option<fs::File> = None;
let rdr = match condition {
Some(file) => csv::Reader::from_reader(Left(file))
None => csv::Reader::from_reader(Right(io::stdin()))
}
for record in rdr.records() {
// Do whatever
}
Note that there is of course some (very minor) overhead of “dispatching” the Read implementation between the Either cases. Most likely irrelevant overhead, but it’s not fully “zero-cost” in all ways. Two separate copies of the code handling the values, e.g. achievable without violating DRY via a generic function, as @zirconium-n mentioned below, would be a different trade-off of more generated code, but no “dispatching” on the enum.