问题描述
我有一个struct
,称为Book
,可以说它将数据存储在书店出售的书上。它需要在某些数据结构中的许多地方被引用(例如,使用Rc
),因此不能以正常方式可变地借用它。但是,它具有某些属性(例如其价格),需要在对象已经具有出色的引用之后,在初始化之后的某个时间填写。
到目前为止,我可以想到两种方法来实现此目的,但是它们都有缺点:
-
内部可变性:给
Book
一个字段,例如price: RefCell<Option<i32>>
,该字段在初始化RefCell::new(Option::None)
时初始化为Book
。稍后,当我们确定书的价格时,可以使用borrow_mut
将price
设置为Some(10)
,然后我们可以borrow
对其进行取回。值。我的感觉是,总的来说,除非有必要,否则我想避免内部可变性,而且似乎在这里似乎不应该是所有必要的。由于需要使用
Option
,因此此技术也有些尴尬,因为价格要到以后才具有值(同时将其设置为0
或-1
,但在我们从逻辑上可以确定价格已经被填满的地方,需要大量match
或unwrap
。 -
单独的表格:根本不将价格存储在
Book
内,而是建立一个单独的数据结构来存储它,例如price_table: HashMap<Rc<Book>,i32>
。有一个函数可以在确定价格后创建并填充此表,然后通过引用(可变或不可变)将其传递给需要了解或更改图书价格的每个函数。像我一样,从C的背景出发,
HashMap
在速度和内存上都感觉像是不必要的开销,对于已经有自然生存空间(在Book
内部)和“应该”的数据通过简单的指针追逐即可访问。这种解决方案还意味着我必须使用一个附加参数(其中包含对price_table
的引用)使许多功能杂乱无章。
在Rust中,这两种方法中的一种通常是惯用的吗,还是有其他方法可以避免这种困境?我确实看到了Once
,但是我不认为这是我想要的,因为我仍然必须在初始化时知道如何填写price
,而我不知道。 / p>
当然,在其他应用程序中,我们可能需要i32
以外的其他类型来表示所需的属性,因此我希望能够处理一般情况。
解决方法
我认为您的第一种方法最适合这种情况。由于您对要写入的某些数据有出色的引用,因此必须在运行时检查借入规则,因此RefCell
是可行的方法。
在RefCell
内部,最好使用Option
或enum
之类的变体Price::NotSet
或自定义Price::Set(i32)
。如果您确实确定所有价格都已在某个时刻初始化,则可以编写方法price()
来为您调用unwrap
或在您的{{1} }包含RefCell
。
我猜想None
的方法在这种情况下会很好,但是如果您希望其中的值不是HashMap
,那么您可能会遇到相同的问题,因为在某处可能有很多关于地图的参考。
我同意Copy
并不是惯用的方法,即使将HashMap
作为值类型,仍然选择您的第一种方法。
编辑:
正如评论中指出的(谢谢!),对于这种情况有两个性能方面的考虑。首先,如果您真的知道,所包含的价格永远不会为零,则可以使用i32
并免费获得std::num::NonZeroU16
变体Option
(请参阅{{ 3}})。
如果您要处理的是None
(例如Copy
)类型,则应该考虑使用i32
而不是Cell
,因为它更轻。有关更详细的比较,请参见documentation
还有两种方法。
-
在结构中使用
Rc<RefCell<<Book>>
的任何地方都使用price: Option<i32>>
。 -
声明一个
strict BookId(usize)
并创建一个library: HashMap<BookId,Book>
。使您的所有参考文献BookId
并因此在需要的地方通过它们间接地参考书籍。