Rust—缓存某些不可变数据的视图的惯用方法
下面是一个简单结构Rust—缓存某些不可变数据的视图的惯用方法,rust,Rust,下面是一个简单结构World的示例代码,其中包含一个Objects的向量,为每个Object分配了一个类别 #[derive(PartialEq, Debug)] enum Category { A, B, C, D } #[derive(Debug)] struct Object { pub someData: f64, pub category: Category //... } struct World { pub objects: Vec<
World
的示例代码,其中包含一个Object
s的向量,为每个Object
分配了一个类别
#[derive(PartialEq, Debug)]
enum Category {
A, B, C, D
}
#[derive(Debug)]
struct Object {
pub someData: f64,
pub category: Category
//...
}
struct World {
pub objects: Vec<Object>,
//...
}
impl World {
pub fn new(objects: Vec<Object>) -> Self {
World { objects }
}
pub fn getObjectsOfCategoryA(&self) -> Vec<&Object> {
self.objects.iter().filter(|x| x.category == Category::A).collect()
}
}
这让我觉得不够理想,因为:
对象
向量的存储模式objectsOfCategoryA
仅仅是对象的一个视图
对象
意外发生变异,则此操作将自动失败。理想情况下,如果在构建了World
之后有任何东西试图改变对象
,我希望出现编译错误objectsOfCategoryA
成为Vec
,我会觉得这是“正确的”,但从我所做的研究来看,这似乎是不可能的
我是新手,所以我很可能是从OOP的角度来看待这个问题的。有人能指出一种实现这种缓存的惯用方法吗?我们不容易生锈,但在这里,我们根本不需要存储引用。我们所需要的只是对象的索引列表get\u objects\u of_category\u a()
只需将索引映射到引用即可
由于对象列表是不可变的,为了简单起见,我选择在构造函数中构建索引列表。它也可以根据需要进行初始化
struct World {
objects: Vec<Object>,
objects_of_category_a: Vec<usize>,
//...
}
impl World {
pub fn new(objects: Vec<Object>) -> Self {
let objects_of_category_a = objects
.iter()
.enumerate()
.filter(|&(_, x)| x.category == Category::A)
.map(|(i, _)| i)
.collect();
World {
objects,
objects_of_category_a,
}
}
pub fn get_objects_of_category_a(&self) -> Vec<&Object> {
self.objects_of_category_a.iter().map(|&i| &self.objects[i]).collect()
}
}
然而,由于这是一个递归数据结构,我们需要某种形式的间接寻址,我在这里使用Box
实现了这种间接寻址。对于平衡树,它还意味着查找元素将在O(logn)
中运行,而索引Vec
则在O(1)
中运行
另一种选择是如上所述将对象存储在
Vec
中,并将索引存储在树中。您希望类别::a
的对象的缓存可以是Vec
类型。这不是惯用的方法,需要修补才能工作。其次是一个类型为Option
的延迟计算缓存。如果世界被宣布为
struct World>,
//...
}
您可以将其初始化为World{objects,None}
,然后当您需要获取Category::A
的对象时,您可以迭代Vec
并填充缓存字段(注意:这需要mut引用,这可以通过内部可变性避免)
pub fn getObjectsOfCategoryA(&'a mut self)->&'a Vec{
如果self.category\u a.是\u none(){
self.category_a=Some(self.objects.iter().filter(|x | x.category==category::a.collect());
}
self.category_a.as_ref().unwrap()
}
您甚至可以通过包装World的objects.push()
来正确更新缓存,从而允许对象发生变异,如下所示
//impl-World{
// ...
pub fn push_inner(&'a mut self,obj:Object){
自我对象推送(obj);
如果self.objects.last().unwrap().category==category::A{
如果让一些(类别a)=&mut self.category\u a{
类别推送(self.objects.last().unwrap())
}
}
}
是指向用于测试此功能的完整代码的链接。确保对象
s不发生变异的一个合理方法是将其设置为非发布字段。如果您希望允许用户读取对象
s,您可以使用对象(&self)->&Vec
方法。如果希望用户具有写访问权限,则可以使用objects\u mut(&mut self)->&mut-Vec
这会使缓存无效。@asky这是一个很好的观点-我会这么做,但这仍然不能回答如何最好地完成缓存部分。是的,这就是为什么它是一个注释,而不是一个答案!你说世界上的对象
是不可变的,但既然它是一个发布字段,你怎么能确定呢?例如,如果这个API的使用者声明了World
作为mut
,对象是可变的。回答得好,但是如果对象不是Vec
,而是更复杂的数据结构,不支持直接索引,那该怎么办?在我的例子中,它是一个自定义树。(如果答案是“不要使用那种数据结构”,或者“向树添加索引”)我想我认为这是完全有效的;)嗯-要求可变引用对我来说似乎是不可接受的,在我的实现中,整个世界的要点是它是不可变的,并且可以在线程之间共享。因此,为了使内部的可变性发挥作用,我们正在考虑要求互斥体使此解决方案可行。在这一点上,Arc
解决方案感觉更好,即使呃,我还是不喜欢。。。
struct World {
objects: Vec<Object>,
objects_of_category_a: Vec<usize>,
//...
}
impl World {
pub fn new(objects: Vec<Object>) -> Self {
let objects_of_category_a = objects
.iter()
.enumerate()
.filter(|&(_, x)| x.category == Category::A)
.map(|(i, _)| i)
.collect();
World {
objects,
objects_of_category_a,
}
}
pub fn get_objects_of_category_a(&self) -> Vec<&Object> {
self.objects_of_category_a.iter().map(|&i| &self.objects[i]).collect()
}
}
enum Path {
/// The target is the current node.
Stop,
/// Set the target to the current node's left subnode.
Left(Box<Path>),
/// Set the target to the current node's right subnode.
Right(Box<Path>),
}