待确定的数据:内部可变性还是单独的HashMap?
Posted
技术标签:
【中文标题】待确定的数据:内部可变性还是单独的HashMap?【英文标题】:Data to be determined later: interior mutability or separate HashMap? 【发布时间】:2021-01-05 23:06:44 【问题描述】:我有一个struct
,称它为Book
,假设它存储了书店出售的书籍的数据。它需要在某些数据结构中的许多地方引用(例如Rc
),因此不能以正常方式可变地借用。但是,它有一些属性,比如它的价格,需要在初始化之后的某个时间填充,在对象已经有未完成的引用之后。
到目前为止,我可以想到两种方法来做到这一点,但它们都有缺点:
内部可变性:给Book
一个字段,例如price: RefCell<Option<i32>>
,当Book
被初始化时,它被初始化为RefCell::new(Option::None)
。之后,当我们确定这本书的价格时,我们可以使用borrow_mut
将price
设置为Some(10)
,然后我们可以borrow
它来检索它的价值。
我的感觉是,一般来说,除非必要,否则人们希望避免内部可变性,而这里似乎不应该是所有必要的。这种技术也有点尴尬,因为Option
,我们需要它,因为价格直到稍后才会有价值(同时将其设置为0
或-1
似乎不像Rust),但是这需要大量的match
es 或unwrap
s 在我们可能在逻辑上确定价格已经被填写的地方。
单独的表:根本不将价格存储在Book
中,而是制作一个单独的数据结构来存储它,例如price_table: HashMap<Rc<Book>, i32>
。有一个函数在价格确定时创建并填充此表,然后通过引用(可变或不可变)将其传递给需要了解或更改图书价格的每个函数。
像我一样来自 C 背景,HashMap
感觉在速度和内存方面都是不必要的开销,因为数据已经有一个自然的位置(在 Book
内部)并且“应该”可以通过简单的指针追逐。此解决方案还意味着我必须使用引用 price_table
的附加参数来混淆许多函数。
这两种方法中的一种在 Rust 中通常更惯用,还是有其他方法可以避免这种困境?我确实看到了Once
,但我认为这不是我想要的,因为我仍然必须在初始化时知道如何填写price
,而我不知道。
当然,在其他应用程序中,我们可能需要除i32
之外的其他类型来表示我们想要的属性,所以我希望能够处理一般情况。
【问题讨论】:
解决像您这样的问题必须从需求开始。您的书店需要支持哪些类型的运营?每种方法都会有一些的缺点;由您决定哪些是重要的。 @trentcl:当然这是一个玩具例子,但是假设书店需要能够收集一堆尚未确定价格的书籍,然后为这些书籍分配价格,然后以后仍然可以访问这些价格来决定向客户收取多少费用。 @trentcl:“每种方法都会有一些缺点” 是的,当然。我的第一个问题是,这两种方法在多大程度上都存在非惯用的缺点。作为该语言的初学者,我对此还没有很好的理解,这就是为什么我要请教专家。我的第二个问题是是否还有其他我不知道的常见选项,它们的缺点对我来说可能不太重要。 吹毛求疵:因为Option<i32>
是Copy
,你可以使用更高效的Cell<Option<i32>>
而不是RefCell
,它会增加运行时检查。
【参考方案1】:
我认为您的第一种方法最适合这种情况。由于您对某些要写入的数据有未完成的引用,您必须在运行时检查借用规则,因此RefCell
是要走的路。
在RefCell
中,更喜欢Option
或自定义enum
以及Price::NotSet
和Price::Set(i32)
等变体。如果您确实确定所有价格都在某个时候初始化,您可以编写一个方法price()
为您调用unwrap
,或者在您的RefCell
包含None
的情况下使用更好的调试输出进行断言.
我猜HashMap
方法适用于这种情况,但如果你想将不是Copy
的东西作为你的价值,你可能会遇到同样的问题,因为可能有突出的对地图某处的引用。
我同意 HashMap
不是这里的惯用方式,仍然选择您的第一种方法,即使 i32
作为值类型。
编辑:
正如 cmets 中指出的(谢谢!),这种情况有两个性能考虑因素。首先,如果您真的知道,包含的价格永远不会为零,您可以使用std::num::NonZeroU16
并免费获得Option
变体None
(参见documentation)。
如果您正在处理Copy
的类型(例如i32
),您应该考虑使用Cell
而不是RefCell
,因为它更轻。更详细的对比见https://***.com/a/30276150/13679671
【讨论】:
另外:如果您的书店从不免费赠送书籍,您可以使用Option<std::num::NonZeroU64>
而不是Option<u64>
。这将具有与u64
相同的内存布局,从而使Option
空闲。
另外请注意,如果 price 确实只是一个整数(或其他 Copy 类型),Cell
在这种情况下提供基本为零的运行时成本和相同的好处。 (LLVM 或 rustc 可能会比没有 Cell 优化它,但肯定比 RefCell 更好)。【参考方案2】:
这里还有两种方法。
在任何地方都使用Rc<RefCell<<Book>>
,在结构中使用price: Option<i32>>
。
声明strict BookId(usize)
并创建library: HashMap<BookId, Book>
。将您的所有参考资料设为BookId
,从而在您需要的任何地方通过它们间接参考书籍。
【讨论】:
以上是关于待确定的数据:内部可变性还是单独的HashMap?的主要内容,如果未能解决你的问题,请参考以下文章
HashMap 是在 Java 内部使用 LinkedList 还是 Array 实现的?