What do I do instead of storing the same base type with two different generics inside of a single variable, it this makes any sense

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.

1 Like

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.

5 Likes

You can also write a generic function yourself, and call that function on each branch.

2 Likes

Thanks! Now the (rhetorical) question is how do I select both answers as solutions :sweat_smile:

Since you don't care which reader you use, you could use a Box<dyn Read> or such.

2 Likes