3
\$\begingroup\$

Here's the result:

use std::env;
use std::error::Error;
use std::ffi::OsString;
use std::process;
use std::vec::Vec;

fn print_line(data: &Vec<Vec<String>>, name: &Option<&String>, col: usize) -> Result<(), Box<dyn Error>>{
    if let Some(n) = name {
        print!("{} = {{",n);
    }else{
        return Err(From::from("incorrect index"));
    }
    
    for record in data {
        if let (Some(id), Some(val)) = (record.get(0), record.get(col)) {
            print!("{}->{}, ",id, val);    
        }else{
            return Err(From::from("incorrect index"));
        }
    }
    println!("}}");
    Ok(())
}

fn run() -> Result<(), Box<dyn Error>> {
    let file_path = get_nth_arg(1)?;
    let mut rdr = csv::ReaderBuilder::new()
        .delimiter(b';')
        .from_path(file_path)?;

    let header: Vec<String> = rdr.headers()?.iter().map(|x| x.to_owned()).collect();
    let data: Vec<Vec<String>> = rdr.records().into_iter().map(|res| 
         (if let Ok(rec) = res {rec.iter().map(|s| s.to_owned()).collect() } else { Vec::new() }))
         .collect();
    for i in 0..header.len(){
        print_line(&data,&header.get(i), i)?;
    }
    Ok(())
}

fn get_nth_arg(i: usize) -> Result<OsString,Box<dyn Error>> {
    match env::args_os().nth(i) {
        None => Err(From::from(format!("expected {} arguments",i))),
        Some(file_path) => Ok(file_path),
    }
}


fn main() {
    if let Err(err) = run() {
        println!("{}", err);
        process::exit(1);
    }
}

Here's some sample content to make a csv that gets parsed by the above program:

P;M;O;W
p1;10;TRUE;5
p2;15;TRUE;6
p3;20;FALSE;7
p4;10;FALSE;8
p5;15;TRUE;9
p6;20;TRUE;5
p7;10;FALSE;6
p8;15;FALSE;7
p9;20;TRUE;8
p10;10;TRUE;9

The output should be:

P = {p1->p1, p2->p2, p3->p3, p4->p4, p5->p5, p6->p6, p7->p7, p8->p8, p9->p9, p10->p10, }
M = {p1->10, p2->15, p3->20, p4->10, p5->15, p6->20, p7->10, p8->15, p9->20, p10->10, }
O = {p1->TRUE, p2->TRUE, p3->FALSE, p4->FALSE, p5->TRUE, p6->TRUE, p7->FALSE, p8->FALSE, p9->TRUE, p10->TRUE, }
W = {p1->5, p2->6, p3->7, p4->8, p5->9, p6->5, p7->6, p8->7, p9->8, p10->9, }

So basically, read the csv, copy its contents to a Vec<Vec<String>>, print data column by column with some extra formatting. Simple enough for a first project in a new language, I thought (and then I spent 2 days getting to this :D)

Questions:

  • is the copy to to Vec<Vec<String>> really needed? I was hoping I could reuse the data in the csv reader, but that does not seem the case.
  • in the print_line function, I had some trouble handling the None case of the Options. Does this rust function look natural to you?
  • are references the right pointer type for print_line parameters?
  • (edit) is it possible to succinctly throw an error instead of creating an empty vector when copying a record from the csv reader fails?
  • any other tips that will avoid headaches in the future?

FWIW, C++ is my main programming language.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

As you suspected, you should try to embrace the library's provided types, as a general rule (if they're practical enough). And you can, here !

For some reason, the reader's methods require mutably borrowing it, but things are clonable in a shorter way than your manual copies.

headers() returns a StringRecord, and that can be iterated to get strings. It's more idiomatic to use iter() to iterate, and enumerate() when you really need an index, rather than iterating using indices/get (and having to handle "invalid index cases"):

let header = rdr.headers()?.clone();
...
for (i, name) in header.iter().enumerate() {
    print_line(&data, name, i)?;
}

(print_line now takes a &str and you don't have to check for None, it just can't happen)

Same with records(): it returns an iterator, and you need to look into it several times due to your algorithm, so you have to copy the items, but you can keep the inner StringRecord's unmolested.

As you suspected too, in case of an error, you should throw it immediately, rather than inventing a fake empty vector, which will fail later (but in a more puzzling way) on some index check. This is really a general programming principle, not specific to Rust: don't propagate corrupt state.

We use the fact that a Vec<Result> can be collected into a Result<Vec>, to make it shorter. Unfortunately, we still need to write some types to help the compiler:

let records: csv::Result<Vec<StringRecord>> = rdr.records().into_iter().collect();
let records = records?;

That's not super important, but I'd also flatten the happy/main case in print_line (remove ifs).

You can also flatten get_nth_arg(), but that's less interesting, since it's not doing anything else than error handling.

Full version:

use std::env;
use std::error::Error;
use std::ffi::OsString;
use std::process;
use std::vec::Vec;
use csv::StringRecord;

fn print_line(records: &Vec<StringRecord>, name: &str, col: usize) -> Result<(), Box<dyn Error>>{
    print!("{} = {{", name);
    for record in records {
        let id = record.get(0).ok_or_else(|| "incorrect index")?;
        let val = record.get(col).ok_or_else(|| "incorrect index")?;
        print!("{}->{}, ",id, val);    
    }
    println!("}}");
    Ok(())
}

fn run() -> Result<(), Box<dyn Error>> {
    let file_path = get_nth_arg(1)?;
    let mut rdr = csv::ReaderBuilder::new()
        .delimiter(b';')
        .from_path(file_path)?;

    let header = rdr.headers()?.clone();
    let records: csv::Result<Vec<StringRecord>> = rdr.records().into_iter().collect();
    let records = records?;
    for (i, name) in header.iter().enumerate() {
        print_line(&records, name, i)?;
    }
    Ok(())
}

fn get_nth_arg(i: usize) -> Result<OsString,Box<dyn Error>> {
    env::args_os().nth(i)
        .ok_or_else(|| format!("expected {} arguments", i).into())
}

fn main() {
    if let Err(err) = run() {
        println!("{}", err);
        process::exit(1);
    }
}

PS: As for the "right pointer type" question, references are good when you need to borrow something (the function will read things, but not hold onto them, or have someone hold onto them, after it returns).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.