rust-csv
rust-csv copied to clipboard
How to refer to a csv::Writer without caring what it's writing to?
What version of the csv crate are you using?
csv 1.1.3
Briefly describe the question, bug or feature request.
I'm trying to implement the common pattern of accepting - to indicate STDOUT/STDIN in CLI arguments representing paths, and running into trouble assigning either the result of a csv::Writer::from_writer or csv::Writer::from_path to the same variable.
Include a complete program demonstrating a problem.
Here's (approximately) what I want to write:
fn run(path: &str) -> Result<(), Box<dyn Error>> {
let mut wtr = match path {
"-" => Box::new(csv::Writer::from_writer(io::stdout())),
path => Box::new(csv::Writer::from_path(path)?),
};
wtr.write_record(&["a", "b", "c"])?;
wtr.flush()?;
Ok(())
}
I've found an acceptable way to do it by adding some indirection:
fn run_generic_inner<W: Write>(mut wtr: csv::Writer<W>) -> Result<(), Box<dyn Error>> {
wtr.write_record(&["a", "b", "c"])?;
wtr.flush()?;
Ok(())
}
fn run_generic(path: &str) -> Result<(), Box<dyn Error>> {
match path {
"-" => run_generic_inner(csv::Writer::from_writer(io::stdout())),
path => run_generic_inner(csv::Writer::from_path(path)?),
}
}
What is the observed behavior of the code above?
The former snippet fails to compile; the latter works correctly. The error:
error[E0308]: `match` arms have incompatible types
--> src/main.rs:9:17
|
7 | let mut wtr = match path {
| ___________________-
8 | | "-" => Box::new(csv::Writer::from_writer(io::stdout())),
| | ------------------------------------------------ this is found to be of type `std::boxed::Box<csv::writer::Writer<std::io::Stdout>>`
9 | | path => Box::new(csv::Writer::from_path(path)?),
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::io::Stdout`, found struct `std::fs::File`
10 | | };
| |_____- `match` arms have incompatible types
|
= note: expected type `std::boxed::Box<csv::writer::Writer<std::io::Stdout>>`
found struct `std::boxed::Box<csv::writer::Writer<std::fs::File>>`
What is the expected or desired behavior of the code above?
I'd like to be able to pass around a reference to a csv::Writer without knowing/caring what it's writing to.
One way to do this would be to add a Writer trait or similar, and I could accept Box<Writer> etc. There might be something much more idiomatic.
(The code with the generic parameter is a little annoying for my use case, though workable.)
Perhaps even better, a cookbook example or even rust-csv support for the --means-STDIN/OUT pattern.
P.S.
Thanks for a great library!
Sure, great question! You have the write idea. The key is that csv::Writer is generic over anything that implements the io::Write trait. It turns out that Box<dyn io::Write> implements the io::Write trait, so you can just use that! For example:
use std::env;
use std::ffi::OsStr;
use std::fs::File;
use std::io;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = env::args_os().nth(1).unwrap_or_else(|| {
OsStr::new("-").to_os_string()
});
let iowtr: Box<dyn io::Write> = if path.to_string_lossy() == "-" {
Box::new(io::stdout())
} else {
Box::new(File::create(path)?)
};
let mut wtr = csv::Writer::from_writer(iowtr);
wtr.write_record(&["foo", "bar", "quux"])?;
wtr.flush()?;
Ok(())
}
And yeah, this would be a great thing to add to the cookbook.