我有一个类似于以下的构造,其中
Analysis
结构负责分析文件,而 Project
结构负责管理文件和依赖项,作为最小的可重现示例:
struct File;
trait Project {
fn get_or_insert(&mut self) -> &File;
}
struct Analysis<'a> {
project: &'a mut dyn Project
}
impl Analysis<'_> {
fn analyze(&mut self) {
let file = self.project.get_or_insert();
self.analyze_parsed_file(file);
}
pub fn analyze_parsed_file(
&mut self,
file: &File
) {}
}
我想做的是分析一个文件。该项目内部有一个加载文件的内存列表,但如果我想要获取的文件不在该列表内,该项目将从磁盘加载它并将其保存到内存中的表示形式。保存是必要的(在我的例子中是为了循环依赖分析)。
提供的示例无法编译,因为我试图多次借用
self
作为可变对象。我的理由如下(但也许这已经是我对借用检查器的理解的极限):
如果我调用
analyze_parsed_file
,该函数可以通过对 file
的可变引用来修改 project
。因此,该示例在 Rust 中是非法的。
现在我知道这永远不会发生,因为该项目永远不会改变文件 - 它只会将它们添加到内部存储中,并且不会覆盖我刚刚获得的文件。
因此,我的问题基本上是:我如何告诉借用检查器?可能可以通过使用某种形式的
unsafe
来解决这个问题,但我宁愿留在安全领域,因为这似乎是一个相当简单的用例。这就是不祥的RefCell
发挥作用的地方吗?
正如我在评论中所说,您需要使
Project::get_or_insert()
需要 &self
而不是 &mut self
。
我们如何做到这一点,同时它需要变异
self
?
关键是这个:
该项目永远不会改变文件 - 它只会将它们添加到内部存储中,并且不会覆盖我刚刚获得的文件
例如,如果您将文件存储在
Vec
中 - 并假设您有某种形式的间接寻址,例如Vec<Box<File>>
(否则增长 Vec
将使所有现有文件无效)- 一切顺利。但编译器不知道这一点。
解决方案是使用一个特殊的集合,一个使用共享引用进行推送的集合。只要不允许删除,这样的集合就是合理的(不过,它可以通过可变引用允许删除)。并且可以使用不安全的代码编写。
幸运的是,您不需要编写不安全的代码 - 因为其他人已经这样做了!输入
elsa
板条箱,它提供各种仅追加的集合。
因此,假设您有
Vec<Box<File>>
,将其替换为 elsa::FrozenVec<Box<File>>
,将 &mut self
替换为 &self
,仅此而已!