Is it sound to Send/Sync a unique Rc?

I've finally caved and am putting together a crate that provides (A)RcBox types that act like Box (i.e. have an impl DerefMut) by being backed by a guaranteed-unique (A)Rc. The intended use case is when some (mutable) initialization has to be done to a reference counted object after allocation to eliminate the Box->(A)Rc conversion cost.

The question is: while an instance of Rc is guaranteed unique, is it sound to Send/Sync it?

My intuition says yes, but I'm not perfectly certain, and even if I were certain that the current standard impl works this way, there's the question of whether this is guaranteed to always be the case.

As a prerequisite, the contained type should also be Send/Sync.

I'm still wary though. If you use Rc: Send to send &mut Rc to another thread, it could be swapped with some other instance from that thread, and now that one is sharing ownership across threads. If you use Rc: Sync to send &Rc to another thread, they could make a new clone, and when you return you again have shared ownership across threads.

A while ago I made this darc crate which lets you dynamically convert between its Rc and Arc. I don't remember what inspired this, but I think the idea came from a discussion on this forum. You could use that Send/Sync an Arc, then convert back to Rc and unshare it when possible.

2 Likes

Note that in my case, it'd be &mut RcBox, and there's no way to go from &mut RcBox -> Rc. If you want to share, you have to go RcBox -> Rc.

I was confused what you meant by "guaranteed-unique (A)Rc" -- what's the point? -- but I think I understand now that you want to eventually unwrap to a normal (A)Rc that's counted again. At that point, your own type would be out of the picture, right?

If that's the case, then I think it's OK to have Send and Sync during that unique period. That safety assumes that Rc will never make any secret clones, which it could, but you're probably fine. :slight_smile:

1 Like

Are you looking for Arc::get_mut()? That'll give you a mutable reference to the thing inside so you can initialize it while there's only one copy of the Arc.

I know it exists, but the "ArcBox pattern" upgrades this to the type system and can skip the check in Arc::get_mut.

I'm even using get_mut:

impl<T: ?Sized> TryFrom<Arc<T>> for ArcBox<T> {
    type Error = Arc<T>;

    fn try_from(mut value: Arc<T>) -> Result<Self, Arc<T>> {
        // Could this just be `Arc::strong_count == 1 && Arc::weak_count == 0`?
        // I _think_ `get_mut` has the weaker synchronization requirements?
        if Arc::get_mut(&mut value).is_some() {
            Ok(ArcBox {
                raw: arc_into_raw_non_null(value),
            })
        } else {
            Err(value)
        }
    }
}

It's about typed intent, and when you want to pass that ArcBox around.

1 Like