待确定的数据:内部可变性还是单独的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_mutprice 设置为Some(10),然后我们可以borrow 它来检索它的价值。

我的感觉是,一般来说,除非必要,否则人们希望避免内部可变性,而这里似乎不应该是所有必要的。这种技术也有点尴尬,因为Option,我们需要它,因为价格直到稍后才会有价值(同时将其设置为0-1 似乎不像Rust),但是这需要大量的matches 或unwraps 在我们可能在逻辑上确定价格已经被填写的地方。

单独的表:根本不将价格存储在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::NotSetPrice::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 实现的?

数据类型的内置方法 可变类型与不可变类型

React 是在内部创建 prevState 的深拷贝还是浅拷贝?

可变类还是不可变类?

类的设计问题

危险!在HashMap中将可变对象用作Key