`Arc::into_inner`

I was looking for a function to get the T from inside an Arc<T> (where T: Sized) and could find one. It would be implemented equivalent to

impl<T: Clone> Arc<T> {
    /// Gets the inner value out of this `Arc` if there is at most `1` strong reference, otherwise
    /// clones the inner value.
    pub fn into_inner(this: Self) -> T {
        Arc::try_unwrap(this).unwrap_or_else(|arc| (*arc).clone())
    }
}

Does it make sense to add this to std?

3 Likes

Just like to bikeshed the name a bit: into_inner implies that it always takes ownership of the inner value. I'd prefer it to be called something like unwrap_or_clone.

11 Likes

A comparison: "Clone if needed" behavior already exists in Arc::make_mut(); this would be an owning analogue of that.

9 Likes

how about calling it make_owned?

7 Likes

How about make_unique? This has symmetry with make_mut. I prefer it over make_owned because we already own the data in the Arc, it's just that we share the ownership. I feel like unwrap_or_clone is a more direct description of what the function does, but doesn't have the symmetry with make_mut.

I'm probably tending towards unwrap_or_clone (which matches the heart contest) because it says what it does. I'll open a PR with that name, then we can bikeshed more there if needs be.

3 Likes

Another verb which has been used is take. I'm not sure if it's exactly used the way we'd want here, but it seems worth noting at least.

2 Likes

I don't know, but I think for this to be justified you need to show this is a common need, and not something that would only be used very rarely. What exactly was the use case? I have used Arc::make_mut, but I don't remember wanting this variation.

1 Like

The make_mut changes the receiver (the Arc) in place and makes it unique too. Because of that I don't think the make_* pattern applies here. It seems a bit like .into_owned() or .to_owned() with preference to the former.

2 Likes

I just found the Cow::into_owned() has exactly same semantics with it - move if owned, clone if shared.

9 Likes

Cow is slightly different in that it calls to_owned rather than clone. That’d be a reason not to use the exact name into_owned. (Alternately, maybe that’s a more useful operation anyway? It supports strictly more types.)

1 Like

Well, I don't think that matters (especially because of the blanket impl ToOwned for T where T: Cloned), but Cow is completely and drastically different from Arc for another reason.

Cow is designed to be single-owner. When you use a Cow, you essentially say "I don't need ownership but I don't mind if I happen to have it". By using Arc, you state that "I need shared ownership". The Cow::into_owned method optimizes for the case where you already have ownership, accounting for the possibility that you didn't, so far. In contrast, with Arc, you unconditionally have (shared) ownership, and the optimization is about uniqueness. So calling it into_owned() doesn't make much sense, because the wrapped value is already owned. make_owned() is subpar for the same reason.

I think take() is completely out of question. It is used for unconditionally stealing a value out of a place (vis. mem::take() and Option::take()), without the hidden possibility of a potentially expensive clone. Furthermore, this proposed method on Arc would not invalidate the other Arcen, but it would consume the one it is being called on – this is also completely inconsistent with the other two takes() accepting a mut ref.

2 Likes

Yes, why not use ToOwned instead of Clone if possible.

What ToOwned is de facto, it's a limited generalization of Clone into including some non-Sized types.

Using the inner ToOwned would let us convert Arc<[T]> to Vec<T>, but I'm not sure how to represent this for the already-owned case of Arc count 1.

2 Likes

*facepalm* Right, it’s to_owned and not into_owned, so even if you could consume the value it wouldn’t help.

It does seem like it might be possible to use specialization to detect the case where <T as ToOwned>::Owned == T (the general case where try_unwrap could be used); along with a special case for Arc<[T]> -> Vec<T> which just does a realloc rather than a copy. But for types outside std, where there's no way to use specialization, if they don't match the case that Clone would satisfy, it would end up just being the equivalent of arc.to_owned().

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.