【问题标题】:Reading ZIP file in Rust causes data owned by the current function在 Rust 中读取 ZIP 文件会导致当前函数拥有数据
【发布时间】:2020-05-05 02:29:29
【问题描述】:

我是 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);
            }
        });
    }
}

非常感谢任何帮助!

【问题讨论】:

标签: rust


【解决方案1】:

archive_contents 似乎阻止了 BufRead 对象返回给调用者。我只是不确定如何解决这个问题。

您必须以某种方式重组代码。这里的问题是,档案数据是档案的一部分。因此,与file 不同,archive_file 不是一个独立的项目,而是一个指向存档本身的排序指针。这意味着存档需要比 archive_file 更长的时间才能使此代码正确。

在 GC 语言中,这不是问题,archive_file 有对 archive 的引用,并且无论它需要多久,它都会保持活动状态。 Rust 不是这样。

解决此问题的一种简单方法是将数据从archive_file 复制出来并复制到一个拥有的缓冲区中,然后您可以将其返回给父级。另一种选择可能是为(archive_contents, item_index) 返回一个包装器,这将委托阅读(虽然可能有点棘手)。还有一个是没有file_reader

【讨论】:

  • 谢谢@Masklinn,你能指点我创建“自有缓冲区”的文档吗?
  • @hooinator 您可以将文件的内容读入 vec,然后将 std::io::Cursor 包裹在 vec 周围。 Cursor 实现了BufRead,因此您应该能够将光标装箱并将其作为特征对象返回。
【解决方案2】:

感谢@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)))
    }
}

【讨论】:

    【解决方案3】:

    虽然您确定的解决方案确实有效,但它也有一些缺点。一是当您从 zip 文件中读取时,您必须在继续之前将要处理的文件的内容读入内存,这对于大文件可能是不切实际的。另一个是在任何一种情况下你都必须堆分配BufReader

    另一种可能更惯用的解决方案是重构您的代码,这样BufReader 根本不需要从函数返回 - 相反,构建您的代码,使其具有打开文件的函数,其中turn 调用处理文件的函数:

    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),
            };
    
        }
    }
    

    这样,您不需要任何Boxes,也不需要在处理之前将文件读入内存。

    如果您有多个需要能够从 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()))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多