【问题标题】:Rust way to design a storage struct and readonly struct usersRust 设计存储结构和只读结构用户的方法
【发布时间】: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 文件路径等)?

目标

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

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

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

精简代码示例

我创建了一个struct BlockReader

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

(playground code)

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&lt;u8&gt;&amp;Vec&lt;u8&gt;Box&lt;Vec&lt;u8&gt;&gt;Box&lt;&amp;Vec&lt;u8&gt;&gt;&amp;Box&lt;&amp;Vec&lt;u8&gt;&gt;&amp;Pin&lt;&amp;Box&lt;&amp;Vec&lt;u8&gt;&gt; 等对“Block”存储进行排列。但是,每个实现置换遇到了各种与借用、生命周期和可变性有关的混杂问题。

同样,我不是在寻找具体的解决方法。我正在寻找一种普遍良好的面向 rust 的设计方法来解决这个一般问题:存储由一些 struct 管理的字节块,让其他 struct 获取对字节块的引用(或指针等),在循环中读取该字节块(同时可能存储新的字节块)。

Rust 专家的问题

锈病专家将如何解决这个问题?
我应该如何将Vec&lt;u8&gt; (Block) 存储在BlockReader.blocks 中,并允许其他Struct 存储他们自己的引用(或Block?
其他 structs 是否应该复制或克隆 Box&lt;Block&gt;Pin&lt;Box&lt;Block&gt;&gt; 或其他内容?
会使用不同的存储,比如固定大小的数组; type Block = [u8; 16384]; 更容易传递引用吗?
其他Struct,如BlockUser1,是否应该被赋予&amp;Block,或Box&lt;Block&gt;,或&amp;Pin&lt;&amp;Box&lt;&amp;Block&gt;,或其他?

同样,每个Vec&lt;u8&gt; (Block) 被写入一次(在BlockReader.readblock 期间),其他Structs 可以通过调用BlockReader.readblock 并稍后通过保存自己的引用/指针/来读取多次等等到那个Block(理想情况下,也许这不理想?)。

【问题讨论】:

  • 听起来您可能正在应用程序级别重新实现操作系统的页面缓存。您是否对简单的文件读取进行了基准测试并确定缓存会提高性能?您可能只需进行常规、简单、冗余的读取即可。如果没有,请考虑memory mapping,它可以让您通过内存访问访问文件,而无需显式读取。
  • "听起来您可能正在重新实现操作系统的页面缓存" 谢谢@JohnKugelman。确实,它看起来是这样的。但是,我有进一步的计划,不仅仅是重做操作系统提供的功能。但为了简单起见,我删除了大部分代码。我将研究内存映射,但现在,我很好奇如何以防锈的方式处理这样的任何程序的更大问题。我猜有很多类似的模式,将字节存储在专用存储 struct 中,然后用不同的 structs 读取这些字节。
  • 您试图存储对局部变量的引用,这是错误的,因为局部变量buffer 将在方法结束时被销毁。你应该存储一个拥有的值
  • 旁注:let mut buffer = Vec&lt;u8&gt;::with_capacity (16384); file.read_to_end (&amp;mut buffer)?;will read read until the end of file, even if that is more than 16kB.

标签: rust


【解决方案1】:

您可以将Vec&lt;u8&gt; 放在Rc&lt;RefCell&lt;...&gt;&gt; 后面,或者如果它们是不可变的,则只需将Rc&lt;..&gt; 放在后面。

如果您需要线程安全访问,则需要使用 Arc&lt;Mutex&lt;...&gt;&gt;Arc&lt;RwLock&lt;...&gt;&gt;

这是您的代码的转换版本。 (有一些拼写错误和位需要更改才能编译 - 你应该在你的示例中真正修复这些错误,并给我们一些几乎可以编译的东西......) 你也可以在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 游乐场链接?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-05
  • 2022-01-15
  • 2015-03-06
  • 2015-10-09
相关资源
最近更新 更多