Is it safe to `Send` struct containing `Rc` if strong_count is 1 and weak_count is 0?
No.
There are multiple points that need to be verified to be able to send Rc
across threads:
- There can be no other handle (
Rc
orWeak
) sharing ownership. - The content of
Rc
must beSend
. - The implementation of
Rc
must use a thread-safe strategy.
Let's review them in order!
Guaranteeing the absence of aliasing
While your algorithm -- checking the counts yourself -- works for now, it would be better to simply ask Rc
whether it is aliased or not.
fn is_aliased<T>(t: &mut Rc<T>) -> bool { Rc::get_mut(t).is_some() }
The implementation of get_mut
will be adjusted should the implementation of Rc
change in ways you have not foreseen.
Sendable content
While your implementation of MyStruct
currently puts String
(which is Send
) into Rc
, it could tomorrow change to Rc<str>
, and then all bets are off.
Therefore, the sendable check needs to be implemented at the Rc
level itself, otherwise you need to audit any change to whatever Rc
holds.
fn sendable<T: Send>(mut t: Rc<T>) -> Result<Rc<T>, ...> {
if !is_aliased(&mut t) {
Ok(t)
} else {
...
}
}
Thread-safe Rc
internals
And that... cannot be guaranteed.
Since Rc
is not Send
, its implementation can be optimized in a variety of ways:
- The entire memory could be allocated using a thread-local arena.
- The counters could be allocated using a thread-local arena, separately, so as to seamlessly convert to/from
Box
. - ...
This is not the case at the moment, AFAIK, however the API allows it, so the next release could definitely take advantage of this.
What should you do?
You could make pack_for_sending
unsafe
, and dutifully document all assumptions that are counted on -- I suggest using get_mut
to remove one of them. Then, on each new release of Rust, you'd have to double-check each assumption to ensure that your usage if still safe.
Or, if you do not mind making an allocation, you could write a conversion to Arc<T>
yourself (see Playground):
fn into_arc(this: Rc) -> Result<Arc, Rc> { Rc::try_unwrap(this).map(|t| Arc::new(t)) }
Or, you could write a RFC proposing a Rc <-> Arc
conversion!
The API would be:
fn Rc<T: Send>::into_arc(this: Self) -> Result<Arc<T>, Rc<T>>
fn Arc<T>::into_rc(this: Self) -> Result<Rc<T>, Arc<T>>
This could be made very efficiently inside std
, and could be of use to others.
Then, you'd convert from MyStruct
to MySendableStruct
, just moving the fields and converting Rc
to Arc
as you go, send to another thread, then convert back to MyStruct
.
And you would not need any unsafe
...