Чтение ZIP-файла в Rust приводит к тому, что данные принадлежат текущей функции - PullRequest
0 голосов
/ 05 мая 2020

Я новичок в Rust и, вероятно, у меня огромный пробел в знаниях. По сути, я надеюсь создать служебную функцию, которая, кроме обычного текстового файла или ZIP-файла, возвращала бы BufRead, где вызывающий может начать обработку построчно. Он хорошо работает для файлов, отличных от ZIP, но я не понимаю, как добиться того же для файлов ZIP. Файлы ZIP будут содержать только один файл в архиве, поэтому я обрабатываю только первый файл в ZipArchive.

Я столкнулся со следующей ошибкой.

error[E0515]: cannot return value referencing local variable `archive_contents`
  --> src/file_reader.rs:30:9
   |
27 |         let archive_file: zip::read::ZipFile = archive_contents.by_index(0).unwrap();
   |                                                ---------------- `archive_contents` is borrowed here
...
30 |         Ok(Box::new(BufReader::with_capacity(128 * 1024, archive_file)))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

Кажется, что archive_contents не позволяет объекту BufRead вернуться к вызывающей стороне. Я просто не уверен, как это обойти.

file_reader.rs

use std::ffi::OsStr;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::path::Path;

pub struct FileReader {
    pub file_reader: Result<Box<BufRead>, &'static str>,
}

pub fn file_reader(filename: &str) -> Result<Box<BufRead>, &'static str> {
    let path = Path::new(filename);
    let file = match File::open(&path) {
        Ok(file) => file,
        Err(why) => panic!(
            "ERROR: Could not open file, {}: {}",
            path.display(),
            why.to_string()
        ),
    };

    if path.extension() == Some(OsStr::new("zip")) {
        // Processing ZIP file.
        let mut archive_contents: zip::read::ZipArchive<std::fs::File> =
            zip::ZipArchive::new(file).unwrap();

        let archive_file: zip::read::ZipFile = archive_contents.by_index(0).unwrap();

        // ERRORS: returns a value referencing data owned by the current function
        Ok(Box::new(BufReader::with_capacity(128 * 1024, archive_file)))
    } else {
        // Processing non-ZIP file.
        Ok(Box::new(BufReader::with_capacity(128 * 1024, file)))
    }
}

main.rs

mod file_reader;

use std::io::BufRead;

fn main() {
    let mut files: Vec<String> = Vec::new();

    files.push("/tmp/text_file.txt".to_string());
    files.push("/tmp/zip_file.zip".to_string());

    for f in files {
        let mut fr = match file_reader::file_reader(&f) {
            Ok(fr) => fr,
            Err(e) => panic!("Error reading file."),
        };

        fr.lines().for_each(|l| match l {
            Ok(l) => {
                println!("{}", l);
            }
            Err(e) => {
                println!("ERROR: Failed to read line:\n  {}", e);
            }
        });
    }
}

Любая помощь приветствуется!

Ответы [ 3 ]

1 голос
/ 05 мая 2020

Кажется, что archive_contents не позволяет объекту BufRead вернуться к вызывающей стороне. Я просто не знаю, как это обойти.

Вы должны как-то реструктурировать код. Проблема здесь в том, что архивные данные являются частью архива. Таким образом, в отличие от file, archive_file не является независимым элементом, это скорее указатель сортировки самого архива. Это означает, что архив должен существовать дольше, чем archive_file, чтобы этот код был правильным.

На языке G C 'd это не проблема, archive_file имеет ссылку на archive и сохранит его в живых столько, сколько потребуется. Не так для Rust.

Простой способ исправить это - просто скопировать данные из archive_file в собственный буфер, который вы можете вернуть родителю. Другой вариант - вернуть оболочку для (archive_contents, item_index), которая делегирует чтение (хотя это может быть несколько сложно). Еще один - не иметь file_reader.

0 голосов
/ 09 мая 2020

Хотя решение, на котором вы остановились, действительно работает, у него есть несколько недостатков. Во-первых, когда вы читаете из zip-файла, вам нужно прочитать содержимое файла, который вы хотите обработать, в память, прежде чем продолжить, что может быть непрактично для большого файла. Другой заключается в том, что в любом случае вам нужно выделить кучу BufReader.

Еще одно, возможно, более идиоматическое решение c - это реструктурировать ваш код, чтобы BufReader не нужно было возвращать из функция вообще - скорее, структурируйте свой код так, чтобы в нем была функция, которая открывает файл, которая, в свою очередь, вызывает функцию, обрабатывающую файл:

use std::ffi::OsStr;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::path::Path;

pub fn process_file(filename: &str) -> Result<usize, String> {
    let path = Path::new(filename);
    let file = match File::open(&path) {
        Ok(file) => file,
        Err(why) => return Err(format!(
            "ERROR: Could not open file, {}: {}",
            path.display(),
            why.to_string()
        )),
    };

    if path.extension() == Some(OsStr::new("zip")) {
        // Handling a zip file
        let mut archive_contents=zip::ZipArchive::new(file).unwrap();
        let mut buf_reader = BufReader::with_capacity(128 * 1024,archive_contents.by_index(0).unwrap());
        process_reader(&mut buf_reader)
    } else {
        // Handling a plain file.
        process_reader(&mut BufReader::with_capacity(128 * 1024, file))
    }

}

pub fn process_reader(reader: &mut dyn BufRead) -> Result<usize, String> {
    // Example, just count the number of lines
    return Ok(reader.lines().count());
}

fn main() {
    let mut files: Vec<String> = Vec::new();

    files.push("/tmp/text_file.txt".to_string());
    files.push("/tmp/zip_file.zip".to_string());

    for f in files {

        match process_file(&f) {
            Ok(count) => println!("File {} Count: {}", &f, count),
            Err(e) => println!("Error reading file: {}", e),
        };

    }
}

Таким образом, вам ничего не нужно Box es, и вам не нужно читать файл в память перед его обработкой.

Недостатком этого решения было бы, если бы у вас было несколько функций, которые должны иметь возможность читать из zip-файлов. Один из способов справиться с этим - определить process_file, который будет использовать функцию обратного вызова для выполнения обработки. Сначала вы измените определение process_file на:

pub fn process_file<C>(filename: &str, process_reader: C) -> Result<usize, String>
    where C: FnOnce(&mut dyn BufRead)->Result<usize, String>

Остальную часть тела функции можно оставить без изменений. Теперь process_reader можно передать в функцию, например:

process_file(&f, count_lines)

где count_lines будет исходной простой функцией, например, для подсчета строк.

Это будет также позволяют передать закрытие:

process_file(&f, |reader| Ok(reader.lines().count()))
0 голосов
/ 07 мая 2020

Спасибо @Masklinn за направление! Вот рабочее решение с их предложением.

file_reader.rs

use std::ffi::OsStr;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Cursor;
use std::io::Error;
use std::io::Read;
use std::path::Path;
use zip::read::ZipArchive;

pub fn file_reader(filename: &str) -> Result<Box<dyn BufRead>, Error> {
    let path = Path::new(filename);
    let file = match File::open(&path) {
        Ok(file) => file,
        Err(why) => return Err(why),
    };

    if path.extension() == Some(OsStr::new("zip")) {
        let mut archive_contents = ZipArchive::new(file)?;

        let mut archive_file = archive_contents.by_index(0)?;

        // Read the contents of the file into a vec.
        let mut data = Vec::new();

        archive_file.read_to_end(&mut data)?;

        // Wrap vec in a std::io::Cursor.
        let cursor = Cursor::new(data);

        Ok(Box::new(cursor))
    } else {
        // Processing non-ZIP file.
        Ok(Box::new(BufReader::with_capacity(128 * 1024, file)))
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...