创建文件流抽象

Creating a file streaming abstraction

这可能是 Rust 的一个超级基本方面,但我正在努力通过参考检查的级别来理解它。

假设我当前的程序对文件中的所有二进制编码整数求和:

pub fn read_int(file: &mut File) -> io::Result<i64> {
  let mut int_buffer = [0; 8];
  file.read_exact(&mut int_buffer)?;
  Ok(i64::from_be_bytes(int_buffer))
}

fn read_loop() -> io::Result<f64> {
    let mut file = File::open("data.dat")?;
    let mut found_end = false;
    let mut dat_sum = 0;
    let mut dat_count = 0;
    while !found_end {
        match read_int(&mut file) {
            Ok(d) => {
                dat_sum   += d;
                dat_count += 1;
            },
            Err(_) => found_end = true
        };
    }
    Ok(dat_sum as f64 / dat_count as f64)
}

我想对此进行抽象以使用专门的 i64 迭代器:

fn read_loop() -> io::Result<f64> {
    let mut int_iter = IntIterator::open("data.dat")?;
    let mut dat_sum = 0;
    let mut dat_count = 0;
    for d in iiter {
        dat_sum   += d;
        dat_count += 1;
    }
    Ok(dat_sum as f64 / dat_count as f64)
}

我尝试实现这个,但是:

pub struct TickIterator<'r> {
    file: &'r mut File,
    found_end: bool
}

impl<'r> TickIterator<'r> {
    pub fn new(fp: String) -> io::Result<TickIterator<'r>> {
      let mut file = File::open(fp)?;
      Ok(TickIterator {
          file: &mut file,
          found_end: false
      })
    }
}

impl<'a> Iterator for TickIterator<'a> {
    type Item = i64;

    fn next(&mut self) -> Option<Self::Item> {
        if self.found_end {
            None
        } else {
            match read_int(self.file) {
                Err(_) => {
                    self.found_end = true;
                    None
                },
                Ok(d) => Some(d)
            }
        }
    }
}

看来我有很多问题要解决;第一个错误似乎是我不能 return 对结构内部 file 的引用,因为它由函数 new 本身拥有。

这是正确的方法吗,还是我的做法有问题?

这是您可以如何执行此操作的一种方法。这里的技巧是通过将类型留给泛型来完全忽略生命周期。我们真的不需要知道我们从中读取的数字是什么,因为我们需要的只是 std::io::Read 来实现这个类型才能工作。通过这种方法,您可以 FileTcpStreamBufReader 以及来自不同板条箱的许多其他类型,这些板条箱也使用 Read&mut T 是一种类似于 T 的类型,如果我们查看文档,Read 是为所有 &mut T where T: Read.

实现的
use std::io::{self, ErrorKind, Read};

pub struct ValueIter<R> {
    reader: R,
    found_end: bool,
}

impl<R: Read> ValueIter<R> {
    pub fn for_reader(reader: R) -> Self {
        ValueIter { 
            reader,
            found_end: false,
        }
    }
}

// `Read` is the trait that gives us `read_exact` 
impl<R: Read> Iterator for ValueIter<R> {
    type Item = io::Result<i64>;
    
    fn next(&mut self) -> Option<Self::Item> {
        if self.found_end {
            return None
        }
        
        let mut buffer = [0u8; 8];
        match self.reader.read_exact(&mut buffer) {
            // Read the next number
            Ok(_) => Some(Ok(i64::from_be_bytes(buffer))),
            // Handle end of file reached
            Err(e) if e.kind() == ErrorKind::UnexpectedEof => {
                self.found_end = true;
                None
            }
            // Return any other errors as part of the iterator
            Err(e) => Some(Err(e)),
        }
    }
}

然后就可以这么用了

pub fn read_loop<R: Read>(reader: &mut R) -> io::Result<i64> {
    let mut sum = 0;
    let mut count = 0;
    
    for value in ValueIter::for_reader(reader) {
        sum += value?;
        count += 1;
    }
    
    Ok(sum / count)
}

整洁的部分是,由于我们在 Read 上实现了它的通用性,我们可以使用的不仅仅是 File。例如,您可以通过将其包装在 BufReader 中来提高性能,这样它就可以从 OS 中以更大的块获取文件,而不必每次都询问 OS 一个新数字已读。

use std::fs::File;
use std::io::BufReader;
let mut file = BufReader::new(File::open("data.dat").expect("Unable to open file"));

println!("{:?}", read_loop(&mut file).unwrap());