When, if ever, does it make sense to use Arc<RefCell<T>>
instead of Arc<Mutex<T>>
?
Not really, since the Arc is there to provide Sync
access but RefCell
is not Sync
.
Edit: For context of this first part of my answer: Of course Rc<RefCell<T>>
is a very sensible (and not uncommon) type, and I assume you don't question that. So this is comparing Arc<RefCell<T>>
not only with Arc<Mutex<T>>
but also with Rc<RefCell<T>>
.
The point of Arc
when compared to Rc
is that the former has atomic (and thus thread-safe) reference counting, which means that, while there's a very slight performance loss due to the need to atomic operations, it gains Send
and Sync
implementations. The precise bounds are
impl<T: ?Sized + Sync + Send> Send for Arc<T> {}
impl<T: ?Sized + Sync + Send> Sync for Arc<T> {}
(side-note: the reason why these require T: Send + Sync
, not just Sync
which is the condition for &T: Sync
or &T: Send
, is that a reference-count-1 Arc
allows extracting the value with try_unwrap
, or mutating the inner value with get_mut
; and even if it didn't have those, the Drop
implementation would have to be executed on a different thread anyways).
Now, RefCell
vs. Mutex
differ in panicking vs. locking, but more importantly in the fact that the latter is thread-safe, while the former isn't. RefCell
not being thread-safe shows in its Sync
implementation
impl<T: ?Sized> !Sync for RefCell<T> {}
Since RefCell<T>
will never be Sync
, this means that Arc<RefCell<T>>
will never be Send
or Sync
, which in turn means, there's no advantage left over Rc<RefCell<T>>
.
By the way, a related question is when Mutex<T>
makes sense, compared to RefCell<T>
, in situations where both could be used. They differ, as mentioned above, in locking vs. panicking and in their Sync
implementation.
impl<T: ?Sized + Send> Sync for Mutex<T> {}
However this means that for a concrete known type T: !Send
, a Mutex<T>
is quite similar to a RefCell<T>
, with identical Send
/Sync
implementations (i.e. they both implement neither). How does their behavior compare then? How useful is a Mutex
if you don't use it between multiple threads?
A Mutex
's locking operation only blocks if another lock is already held. But if the Mutex
isn't Sync
, then no other thread can be accessing the Mutex
at the same time; this means that once .lock()
blocks, it's also always automatically going to be a deadlock situation (for that particular thread). In this sense, RefCell<T>
is like a non-Sync
Mutex
but with deadlock detection, so the panicking can be seen as an (almost) always advantageous behavior, at least, assuming that deadlocks are never intended behavior.
The last difference then is poisoning. But a Mutex
that only a single thread accesses is hard to get poisoned anway. AFAICT, it's really only possible if you use catch_unwind
, otherwise a panic that poisons the Mutex
would also take down the whole thread, which is the only thread that has access to the Mutex
in the first place.
Of course Mutex<T>
with T: !Send
could potentially make sense if it's part of generic code, where only parts of the API have a T: Send
bounds, and other, still useful, parts don't (but still contain a Mutex
).
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.