看着你的问题,我想你是想说你很困惑为什么<R: BufRead> 是必要的,或者为什么这甚至有效。
在你的例子中,这个 generic 并不是绝对必要的。可以像这样实现您描述的功能:
use std::{fs, io};
fn main() -> io::Result<()> {
let mut file = fs::File::open("./path/to/file")?;
let bytes = read_eight_bytes(&mut file)?;
println!("{:?}", bytes);
Ok(())
}
fn read_eight_bytes(file: &mut fs::File) -> io::Result<[u8; 8]> {
use io::Read;
let mut bytes = [0; 8];
file.read_exact(&mut bytes)?;
Ok(bytes)
}
Playground
这是完全有效的,希望应该是有意义的。
但是,为什么fn read_eight_bytes<R: BufRead>(file: &mut R) -> [u8; 8] 有效?首先,我假设您了解以下概念:
了解上述概念后,您应该知道,这种语法意味着函数read_eight_bytes 是一个泛型函数,其泛型类型名为R。然后您还应该了解 generic 具有 trait bound,需要类型 R 来实现 BufRead。并且该函数接受一个参数,该参数是对变量file 的可变引用,该变量的类型为R。
现在看一下BufRead 的定义:我们看到它包含几个函数。但令人惊讶的是没有read_exact 功能!为什么这样的函数会编译?
use std::{fs, io};
use io::BufRead;
fn main() -> io::Result<()> {
let file = fs::File::open("./path/to/file")?;
let mut reader = io::BufReader::new(file);
let bytes = read_eight_bytes(&mut reader)?;
println!("{:?}", bytes);
Ok(())
}
fn read_eight_bytes<R: BufRead>(reader: &mut R) -> io::Result<[u8; 8]> {
let mut bytes = [0; 8];
reader.read_exact(&mut bytes)?;
Ok(bytes)
}
Playground
注意:我已将返回类型更改为io::Result<...>。与unwraping 每Result 相比,这被认为是一种更好的做法。
我还更改了函数调用以使用BufReader,因为BufReader 实现BufRead 而File 没有。我将在下面进一步介绍差异。
之所以有效是因为BufRead 是Super Trait。这意味着任何实现BufRead 的类型也必须实现Read。因此它必须具有read_exact 功能!
鉴于我们的函数从不需要 BufRead 上的函数,我们可以将 trait 绑定更改为仅需要 Read:
use std::{fs, io};
use io::Read;
fn main() -> io::Result<()> {
let file = fs::File::open("./path/to/file")?;
let mut reader = io::BufReader::new(file);
let bytes = read_eight_bytes(&mut reader)?;
println!("{:?}", bytes);
Ok(())
}
fn read_eight_bytes<R: Read>(reader: &mut R) -> io::Result<[u8; 8]> {
let mut bytes = [0; 8];
reader.read_exact(&mut bytes)?;
Ok(bytes)
}
Playground
现在这里有一些关于此更改的有趣内容。 read_eight_bytes 函数现在可以(至少)以两种不同的方式调用:
use std::{fs, io};
use io::Read;
fn main() -> io::Result<()> {
let mut file = fs::File::open("./path/to/file")?;
let bytes = read_eight_bytes(&mut file)?;
println!("{:?}", bytes);
let file = fs::File::open("./path/to/file")?;
let mut reader = io::BufReader::new(file);
let bytes = read_eight_bytes(&mut reader)?;
println!("{:?}", bytes);
Ok(())
}
fn read_eight_bytes<R: Read>(reader: &mut R) -> io::Result<[u8; 8]> {
let mut bytes = [0; 8];
reader.read_exact(&mut bytes)?;
Ok(bytes)
}
Playground
这是为什么?这是因为 File 和 BufReader 都实现了 Read 特征。因此两者都可以与read_eight_bytes 函数一起使用!
那么为什么有人要使用File 或BufReader 而不是另一个呢?
BufReader 文档对此进行了解释:
BufReader 结构为任何读取器添加缓冲。
直接使用 Read 可能效率极低
实例。例如,对 TcpStream 的每次读取调用都会导致
系统调用。 BufReader 在
底层读取并维护结果的内存缓冲区。
BufReader 可以提高小程序的速度
对同一文件或网络套接字的重复读取调用。它不是
一次阅读大量内容或仅阅读一个或一个
几次。从源读取时它也没有任何优势
已经在内存中了,就像一个 Vec。
现在,还记得我们之前是如何为File 类型编写这个函数的吗?人们想要用泛型编写它的主要原因是调用者可以做出上述选择。这是图书馆的常见做法,这种选择确实很重要。然而,泛型是以增加compile times(过度使用时)和增加代码复杂性为代价的。