创建文件流抽象
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
来实现这个类型才能工作。通过这种方法,您可以 File
、TcpStream
、BufReader
以及来自不同板条箱的许多其他类型,这些板条箱也使用 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());
这可能是 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
来实现这个类型才能工作。通过这种方法,您可以 File
、TcpStream
、BufReader
以及来自不同板条箱的许多其他类型,这些板条箱也使用 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());