0

I'm a little bit stuck with a in a first glance so simple problem. I want to write a simple Rust program that depending on the file extension can read normal CSV files as well as zipped CSV files and outputs the content of these files. Well, of course, the real problem is a little bit differnt but I'm stuck before I can even start to solve the actual problem. :-D

What I tried was something like this:

use std::io::Error;
use std::fs::File;
use std::io;
use csv::Reader;

fn read_csv(filename: &str) -> Result<(), Error> {
    let f = std::fs::File::open(filename)?;

    // Want to return a reader object which either reads from ZipFile or from "normal" File
    let mut rdr: Reader<Box<dyn io::Read>> =
        if filename.ends_with("zip") { // zip case
            let mut ar = zip::read::ZipArchive::new(f)?;
            let zf = ar.by_index(0)?;
            csv::ReaderBuilder::new().delimiter(b',').from_reader(Box::new(zf))
        } else { // csv case
            csv::ReaderBuilder::new().delimiter(b',').from_reader(Box::new(f))
        };

    // From here on, I don't want to care whether I'm reading from a zipped CSV or normal CSV
    for rec in rdr.records() {
        rec?.iter().for_each(|v| println!("{},", v));
    }

    Ok(())
}

fn main() -> Result<(), Error> {
    let filename_csv = "tmp.csv";
    let filename_zip = "tmp.csv.zip"; // same as tmp.csv but zipped

    read_csv(filename_csv);
    read_csv(filename_zip);

    Ok(())
}

I.e., depending on the filename, I'm trying to either create a CSV reader from a ZipFile object or from a normal File object. However, when I try to compile this program, the compiler correctly tells me:

error[E0597]: `ar` does not live long enough
  --> src/main.rs:13:22
   |
10 |     let mut rdr: Reader<Box<dyn io::Read>> =
   |         ------- borrow later stored here
...
13 |             let zf = ar.by_index(0)?;
   |                      ^^ borrowed value does not live long enough
14 |             csv::ReaderBuilder::new().delimiter(b',').from_reader(Box::new(zf))
15 |         } else { // csv case
   |         - `ar` dropped here while still borrowed

error: aborting due to previous error; 1 warning emitted

For more information about this error, try `rustc --explain E0597`.
error: could not compile `zipped-csv-read`

This makes sense to me, since ar will "die" as soon as the if-block is finished and then it cannot be used in the reader object. But what I do not understand how to restructure the code such that it becomes safe but without too much boilerplate code (like moving the for-loop inside of both if-else-branches). I'm coming from the Python, Java, ... world where the above code would "just work". I'm glad that Rust shows me that Python and Java programs would then probably actually suck from the safety point of view... but I need a hint from someone with Rust experience how to make the code better.

Best and thanks, Sergej

P.S.: Dependencies in Cargo.toml (just for completeness):

[dependencies]
zip = "0.5"
csv = "1.1"
Sergej F.
  • 23
  • 4
  • The question was associated with: [Are polymorphic variables allowed?](https://stackoverflow.com/questions/28219519/are-polymorphic-variables-allowed), however, my problem is not that I don't know how to assign objects of two different types (but which implement the same trait) to a variable but that I had a problem with lifetime of the variable. – Sergej F. Oct 28 '20 at 20:05

1 Answers1

1

As you note, a variable gets dropped at the end of the scope where it's declared.

Fortunately, you can declare a variable in a different scope to where you assign it a value, so you could write something like:

use std::io::Error;
use std::fs::File;
use std::io;
use csv::Reader;

fn read_csv(filename: &str) -> Result<(), Error> {
    let f = std::fs::File::open(filename)?;

    let mut ar; 

    // Want to return a reader object which either reads from ZipFile or from "normal" File
    let mut rdr: Reader<Box<dyn io::Read>> =
        if filename.ends_with("zip") { // zip case
            ar = zip::read::ZipArchive::new(f)?;
            let zf = ar.by_index(0)?;
            csv::ReaderBuilder::new().delimiter(b',').from_reader(Box::new(zf))
        } else { // csv case
            csv::ReaderBuilder::new().delimiter(b',').from_reader(Box::new(f))
        };  

    // From here on, I don't want to care whether I'm reading from a zipped CSV or normal CSV
    for rec in rdr.records() {
        rec?.iter().for_each(|v| println!("{},", v));
    }   

    Ok(())
}

fn main() -> Result<(), Error> {
    let filename_csv = "tmp.csv";
    let filename_zip = "tmp.csv.zip"; // same as tmp.csv but zipped

    read_csv(filename_csv);
    read_csv(filename_zip);

    Ok(())
}

That moves where ar is declared, so it will get dropped at the end of read_csv instead of at the end of the if block.

Daniel Wagner-Hall
  • 2,446
  • 1
  • 20
  • 18