Rust如何设计一个存储结构和只读结构体?

5

简而言之:在 Rust 中,如何以最佳方式创建一些字节存储(例如 Vec<u8>),将该 Vec<u8> 存储在可以使用键值(类似于 BTreeMap<usize, &Vec<u8>>)访问的 struct 字段中,并从其他 struct 中读取这些 Vec<u8>
      这是否可以推广为类似的 struct 的一般良好 rust 设计,用作字节块(Vec<u8>[u8; 16384] 等)的存储和缓存,可通过关键字(usize 偏移量、u32 索引、String 文件路径等)进行访问?

目标

我正在尝试创建一个字节存储 structimpl 函数,其中:

  1. 按需从磁盘上每次读取 16384 个字节到容量为 16384 的 Vec<u8>“块”中
  2. 其他 struct 将分析各种 Vec<u8>,可能需要存储对这些“块”的自己的引用
  3. 高效:只在内存中拥有一个“块”的副本,避免不必要的复制、克隆等

不幸的是,对于每个实现尝试,我都会遇到借用、生命周期省略、可变性、复制或其他问题。

简化代码示例

我创建了一个名为 BlockReaderstruct

  1. 创建一个类型为 BlockVec<u8>Vec<u8>::with_capacity(16384)
  2. 从文件中读取(使用 File::seekFile::take::read_to_end)并将 16384 个 u8 存储到一个 Vec<u8>
  3. Vec<u8> 的引用存储在作为 Blocks 类型的 BTreeMap

playground 代码

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.readblockstruct 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 应该被赋予 &BlockBox<Block>&Pin<&Box<&Block> 还是其他内容的引用?
每个 Vec<u8> (Block) 只写入一次(在 BlockReader.readblock期间),并且可以通过调用 BlockReader.readblock 多次被其他 Struct 读取,并稍后保存其自己的对该 Block 的引用/指针等。(理想情况下,这可能不是最理想的。)

2
实现缓存是棘手的。请参见:内部可变性如何用于缓存行为? - John Kugelman
听起来你可能正在应用程序级别重新实现操作系统的页面缓存。你是否对简单文件读取进行了基准测试,并确定缓存会提高性能?你可能只需要进行常规、不复杂、冗余的读取就可以了。如果不行,考虑使用内存映射,这将让你通过内存访问访问文件,而无需显式读取。 - John Kugelman
1
附注:let mut buffer = Vec<u8>::with_capacity(16384); file.read_to_end(&mut buffer)?; 将读取整个文件,即使超过16KB。 - Jmb
嗨@Jmb,在我的初步测试中,read_to_end 对我有效。我认为这是因为我限制了向量的容量。 - JamesThomasMoon
你并没有限制向量的容量。你只是预先分配了一个给定的大小,但是如果有更多的数据需要读取,“read_to_end”可以自由增加该大小。你的代码仅在读取文件中的最后一个数据块时才能正常工作。 - Jmb
显示剩余6条评论
1个回答

3

如果它们是不可变的,您可以将Vec<u8>置于Rc<..>Rc<RefCell<...>>后面。

如果您需要线程安全访问,则需要使用Arc<Mutex<...>>Arc<RwLock<...>>

这是您代码转换后的版本。 (有一些拼写错误和细节需要更改才能进行编译-您真的应该在示例中纠正这些问题,并给我们提供一个几乎可以编译的示例...) 您还可以在playground中查看此内容。

use std::io::Seek;
use std::io::SeekFrom;
use std::io::Read;
use std::fs::File;
use std::cell::RefCell;
use std::rc::Rc;
use std::collections::BTreeMap;

type Block = Vec<u8>;
type Blocks = BTreeMap<usize, Rc<RefCell<Block>>>;

pub struct BlockReader {
    blocks: Blocks,
    file: File,
}

impl BlockReader {
    /// 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<Rc<RefCell<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].clone());
        }
        // 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, Rc::new(RefCell::new(buffer)));
        Ok(self.blocks[&offset].clone())
    }
}

pub struct BlockAnalyzer1 {
   pub blockreader: BlockReader,
}

impl BlockAnalyzer1 {
    /// contrived example function
    pub fn doStuff(&mut self) -> Result<bool,std::io::Error> {
        let mut b: Rc<RefCell<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)
    }
}

我添加了一个 Rust Playground 链接。 - JamesThomasMoon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接