I have a struct, call it Book, which let's say stores data on a book sold by a bookstore. It needs to be referenced at many places in some data structure (e.g. with Rc) and so cannot be borrowed mutably in the normal way. However, it has some attribute, say its price, that needs to be filled in at some time later than initialization, after the object already has outstanding references.
So far I can think of two ways to do this, but they both have disadvantages:
Interior mutability: give
Booka field such asprice: RefCell<Option<i32>>which is initialized toRefCell::new(Option::None)whenBookis initialized. Later on, when we determine the price of the book, we can useborrow_mutto setpricetoSome(10)instead, and from then on we canborrowit to retrieve its value.My sense is that in general, one wants to avoid interior mutability unless necessary, and it doesn't seem here like it ought to be all that necessary. This technique is also a little awkward because of the
Option, which we need because the price won't have a value until later (and setting it to0or-1in the meantime seems un-Rustlike), but which requires lots ofmatches orunwraps in places where we may be logically certain that the price will have already been filled in.Separate table: don't store the price inside
Bookat all, but make a separate data structure to store it, e.g.price_table: HashMap<Rc<Book>, i32>. Have a function which creates and populates this table when prices are determined, and then pass it around by reference (mutably or not) to every function that needs to know or change the prices of books.Coming from a C background as I do, the
HashMapfeels like unnecessary overhead both in speed and memory, for data that already has a natural place to live (insideBook) and "should" be accessible via a simple pointer chase. This solution also means I have to clutter up lots of functions with an additional argument that's a reference toprice_table.
Is one of these two methods generally more idiomatic in Rust, or are there other approaches that avoid the dilemma? I did see Once, but I don't think it's what I want, because I'd still have to know at initialization time how to fill in price, and I don't know that.
Of course, in other applications, we may need some other type than i32 to represent our desired attribute, so I'd like to be able to handle the general case.
I think that your first approach is optimal for this situation. Since you have outstanding references to some data that you want to write to, you have to check the borrowing rules at runtime, so
RefCellis the way to go. Inside theRefCell, prefer anOptionor a customenumwith variants likePrice::NotSetandPrice::Set(i32). If you are really sure, that all prices are initialized at some point, you could write a methodprice()that callsunwrapfor you or does an assertion with better debug output in the case yourRefCellcontains aNone.I guess that the
HashMapapproach would be fine for this case, but if you wanted to have something that is notCopyas your value in there, you could run into the same problem, since there might be outstanding references into the map somewhere.I agree that the
HashMapwould not be the idiomatic way to go here and still choose your first approach, even withi32as the value type.Edit:
As pointed out in the comments (thanks you!), there are two performance considerations for this situation. Firstly, if you really know, that the contained price is never zero, you can use
std::num::NonZeroU16and get theOptionvariantNonefor free (see documentation).If you are dealing with a type that is
Copy(e.g.i32), you should consider usingCellinstead ofRefCell, because it is lighter. For a more detailed comparison, see https://stackoverflow.com/a/30276150/13679671