So I was thinking a bit about Cell
and RefCell
. I often find that people are not sure of the best way to use them – particularly RefCell
– and the problem is that if you use them wrong, you are likely to get panics and frustration.
Background: my philosophy on cell and ref-cell
At its most simple, my advice is usually as follows: use Cell
when the data is copy; otherwise, only hold the RefCell
lock long enough to store some data in or to clone the data out and return it. If the data is too expensive to clone regularly, use an Rc
or Arc
so it becomes cheap. Further, you should package up all access to those fields in simple accessors so you can easily audit what is being done.
More generally, the idea is to avoid holding the lock and then doing complex things. So e.g. returning a Ref
or invoking a closure while holding the lock is to be done with care, because you can’t easily audit what will be done while the lock is held. Likewise, invoking other methods on self
is risky, since those methods may evolve to be quite complex and may start trying to use other fields.
Note that this effectively models what you can do in most GC-languages, like Java or Ocaml, where you can’t take the address of a field, just load from it or store to it. This is no accident, I think, since aliased-mutability is so omnipresent in those languages. (IOW, every field in a Java class is effectively a Cell<Gc<T>>
, where Gc<T>
is some Copy
wrapper type for Gc-managed memory.)
A “magic cell” that only offers get/set
This leads me to an interesting thought. I think that the Cell
type, with its get
and set
APIs, represents a better interface than RefCell
. It avoids the danger of holding the lock too long and I think is just generally more intuitive. Unfortunately, it is only applicable to Copy
types.
What if we offered a single type, let’s call it MagicCell
, that supports the Cell
API (i.e., get
and set
) but works for all Clone
data. I was thinking it would use specialization so that, internally, it uses a Cell
if the data is Copy
and a RefCell
otherwise. We can then promote this type as the Preferred Way to handle aliased-mutability (that is, market RefCell
as a kind of specialty tool for more advanced scenarios).
What we could even do, which would probably be better, is to just make cell itself be this “magic” type. IOW, all existing uses of Cell
continue to work (and have no space overhead), but you can now also use it for Clone
data (at the cost of a flag).
(We might then want to have a variant, ReflessCell
, that guarantees no flag, but works as Cell
today, except that it should also support a swap
method. This could used if you really want to optimize.)