简而言之:在 Rust 中,如何以最佳方式创建一些字节存储(例如 Vec<u8>
),将该 Vec<u8>
存储在可以使用键值(类似于 BTreeMap<usize, &Vec<u8>>
)访问的 struct
字段中,并从其他 struct
中读取这些 Vec<u8>
?
这是否可以推广为类似的 struct
的一般良好 rust 设计,用作字节块(Vec<u8>
、[u8; 16384]
等)的存储和缓存,可通过关键字(usize
偏移量、u32
索引、String
文件路径等)进行访问?
目标
我正在尝试创建一个字节存储 struct
和 impl
函数,其中:
- 按需从磁盘上每次读取 16384 个字节到容量为 16384 的
Vec<u8>
“块”中 - 其他
struct
将分析各种Vec<u8>
,可能需要存储对这些“块”的自己的引用 - 高效:只在内存中拥有一个“块”的副本,避免不必要的复制、克隆等
不幸的是,对于每个实现尝试,我都会遇到借用、生命周期省略、可变性、复制或其他问题。
简化代码示例
我创建了一个名为 BlockReader
的 struct
:
- 创建一个类型为
Block
的Vec<u8>
(Vec<u8>::with_capacity(16384)
) - 从文件中读取(使用
File::seek
和File::take::read_to_end
)并将 16384 个u8
存储到一个Vec<u8>
中 - 将
Vec<u8>
的引用存储在作为Blocks
类型的BTreeMap
中
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])
}
}
示例用例问题
每个实现都存在很多问题。例如,BlockReader.readblock
被 struct BlockAnalyzer1
调用两次引起了无尽的困难:
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 专家的问题: 1. Rust 专家如何处理这个问题? 2. 我应该如何在
BlockReader.blocks
中存储 Vec<u8>
(Block
),并允许其他 Struct
存储自己的引用(或指针,或指向指针的引用,或定位 Box 指针等)到 Block
中?
3. 其他struct
应该复制或克隆一个 Box<Block>
或一个 Pin<Box<Block>>
,还是其他什么东西?
4. 使用不同的存储方式如固定大小的数组;type Block = [u8; 16384];
是否更容易传递引用?
5. 类似于 BlockUser1
的其他 Struct
应该被赋予 &Block
、Box<Block>
、&Pin<&Box<&Block>
还是其他内容的引用?每个
Vec<u8>
(Block
) 只写入一次(在 BlockReader.readblock
期间),并且可以通过调用 BlockReader.readblock
多次被其他 Struct
读取,并稍后保存其自己的对该 Block
的引用/指针等。(理想情况下,这可能不是最理想的。)
let mut buffer = Vec<u8>::with_capacity(16384); file.read_to_end(&mut buffer)?;
将读取整个文件,即使超过16KB。 - Jmbread_to_end
对我有效。我认为这是因为我限制了向量的容量。 - JamesThomasMoon