【发布时间】:2021-08-31 15:17:51
【问题描述】:
tl;dr 什么是最好的“Rust 方式”来创建一些字节存储,在这种情况下是 Vec<u8>,将 Vec<u8> 存储在可以访问的 struct 字段中一个键值(如BTreeMap<usize, &Vec<u8>>),然后从其他structs 中读取那些Vec<u8>?
这是否可以推断为类似structs 的一般良好防锈设计,它们充当字节块(Vec<u8>、[u8; 16384] 等)的存储和缓存,可通过键(usize 偏移量,a u32 索引、String 文件路径等)?
目标
我正在尝试创建一个字节存储 struct 和 impl 函数:
- 将按需从磁盘读取的 16384 字节存储到容量为 16384 的
Vec<u8>的“块”中 - 其他
struct将分析各种Vec<u8>并可能需要存储自己对这些“块”的引用 - 高效:内存中只有一个“块”的副本,避免不必要的复制、克隆等。
不幸的是,对于每次实施尝试,我都会遇到借用、生命周期省略、可变性、复制或其他问题的难题。
精简代码示例
我创建了一个struct BlockReader
- 创建一个
Vec<u8>(Vec<u8>::with_capacity(16384)) 类型为Block - 从文件中读取(使用
File::seek和File::take::read_to_end)并将u8中的16384个存储到Vec<u8>中 - 在类型为
Blocks的BTreeMap中存储对Vec<u8>的引用
use std::io::Seek;
use std::io::SeekFrom;
use std::io::Read;
use std::fs::File;
use std::collections::BTreeMap;
type Block = Vec<u8>;
type Blocks<'a> = BTreeMap<usize, &'a Block>;
pub struct BlockReader<'a> {
blocks: Blocks<'a>,
file: File,
}
impl<'a> BlockReader<'a> {
/// read a "block" of 16384 `u8` at file offset
/// `offset` which is multiple of 16384
/// if the "block" at the `offset` is cached in
/// `self.blocks` then return a reference to that
/// XXX: assume `self.file` is already `open`ed file
/// handle
fn readblock(& mut self, offset: usize) -> Result<&Block, std::io::Error> {
// the data at this offset is the "cache"
// return reference to that
if self.blocks.contains_key(&offset) {
return Ok(&self.blocks[&offset]);
}
// have not read data at this offset so read
// the "block" of data from the file, store it,
// return a reference
let mut buffer = Block::with_capacity(16384);
self.file.seek(SeekFrom::Start(offset as u64))?;
self.file.read_to_end(&mut buffer);
self.blocks.insert(offset, & buffer);
Ok(&self.blocks[&offset])
}
}
示例用例问题
每个实现都存在许多问题。例如,struct BlockAnalyzer1 两次调用BlockReader.readblock 造成了无穷无尽的困难:
pub struct BlockAnalyzer1<'b> {
pub blockreader: BlockReader<'b>,
}
impl<'b> BlockAnalyzer1<'b> {
/// contrived example function
pub fn doStuff(&mut self) -> Result<bool, std::io::Error> {
let mut b: &Block;
match self.blockreader.readblock(3 * 16384) {
Ok(val) => {
b = val;
},
Err(err) => {
return Err(err);
}
}
match self.blockreader.readblock(5 * 16384) {
Ok(val) => {
b = val;
},
Err(err) => {
return Err(err);
}
}
Ok(true)
}
}
结果
error[E0597]: `buffer` does not live long enough
--> src/lib.rs:34:36
|
15 | impl<'a> BlockReader<'a> {
| -- lifetime `'a` defined here
...
34 | self.blocks.insert(offset, & buffer);
| ---------------------------^^^^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `buffer` is borrowed for `'a`
35 | Ok(&self.blocks[&offset])
36 | }
| - `buffer` dropped here while still borrowed
但是,对于这种设计的不同排列,我遇到了许多其他错误,例如我遇到的另一个错误
error[E0499]: cannot borrow `self.blockreader` as mutable more than once at a time
--> src/main.rs:543:23
|
463 | impl<'a> BlockUser1<'a> {
| ----------- lifetime `'a` defined here
...
505 | match self.blockreader.readblock(3 * 16384) {
| ---------------------------------------
| |
| first mutable borrow occurs here
| argument requires that `self.blockreader` is borrowed for `'a`
...
543 | match self.blockreader.readblock(5 * 16384) {
| ^^^^^^^^^^^^^^^^ second mutable borrow occurs here
在BlockReader 中,我尝试使用Vec<u8>、&Vec<u8>、Box<Vec<u8>>、Box<&Vec<u8>>、&Box<&Vec<u8>>、&Pin<&Box<&Vec<u8>> 等对“Block”存储进行排列。但是,每个实现置换遇到了各种与借用、生命周期和可变性有关的混杂问题。
同样,我不是在寻找具体的解决方法。我正在寻找一种普遍良好的面向 rust 的设计方法来解决这个一般问题:存储由一些 struct 管理的字节块,让其他 struct 获取对字节块的引用(或指针等),在循环中读取该字节块(同时可能存储新的字节块)。
Rust 专家的问题
锈病专家将如何解决这个问题?
我应该如何将Vec<u8> (Block) 存储在BlockReader.blocks 中,并允许其他Struct 存储他们自己的引用(或Block?
其他 structs 是否应该复制或克隆 Box<Block> 或 Pin<Box<Block>> 或其他内容?
会使用不同的存储,比如固定大小的数组; type Block = [u8; 16384]; 更容易传递引用吗?
其他Struct,如BlockUser1,是否应该被赋予&Block,或Box<Block>,或&Pin<&Box<&Block>,或其他?
同样,每个Vec<u8> (Block) 被写入一次(在BlockReader.readblock 期间),其他Structs 可以通过调用BlockReader.readblock 并稍后通过保存自己的引用/指针/来读取多次等等到那个Block(理想情况下,也许这不理想?)。
【问题讨论】:
-
听起来您可能正在应用程序级别重新实现操作系统的页面缓存。您是否对简单的文件读取进行了基准测试并确定缓存会提高性能?您可能只需进行常规、简单、冗余的读取即可。如果没有,请考虑memory mapping,它可以让您通过内存访问访问文件,而无需显式读取。
-
"听起来您可能正在重新实现操作系统的页面缓存" 谢谢@JohnKugelman。确实,它看起来是这样的。但是,我有进一步的计划,不仅仅是重做操作系统提供的功能。但为了简单起见,我删除了大部分代码。我将研究内存映射,但现在,我很好奇如何以防锈的方式处理这样的任何程序的更大问题。我猜有很多类似的模式,将字节存储在专用存储
struct中,然后用不同的structs 读取这些字节。 -
您试图存储对局部变量的引用,这是错误的,因为局部变量
buffer将在方法结束时被销毁。你应该存储一个拥有的值 -
旁注:
let mut buffer = Vec<u8>::with_capacity (16384); file.read_to_end (&mut buffer)?;will read read until the end of file, even if that is more than 16kB.
标签: rust