Arc and Pin?

Uh, so this might actually be a bug, arguably? But anyway. We looked into Arc and Pin and the API seems either completely pointless or severely lacking.

So to start you use Arc::pin(foo). And you can Pin::clone(...) the resulting Pin<Arc<...>>...

But as you start to dig into it, there's a lack of Arc::pin_cyclic, there's a whole effectively unused weak counter in a Pin<Arc<...>> because you can't downgrade a Pin<Arc<...>>, and it just in general feels like, if you ever reach for Pin<Arc<...>>, you're actually using the wrong abstraction?

We have a lot of questions about all this. "If Pin is about self-references, why would anyone use Arc::pin over Arc::new_cyclic?", "Why's there no downgrade for these?", among others. These don't seem to be answered in any of the existing material. They're definitely not answered on the github, there's nothing that talks about Arc, Pin and downgrade either on the issues or the pull requests. There's not enough rationale for using Arc::pin anywhere in the docs. It seems that Arc::pin exists purely because Box::pin exists (unless we missed some (un)obvious pull request somewhere)!

What are we missing, if anything? If we're not missing anything... what does this all mean? These APIs are stable, so... now what?

In theory it would be perfectly logical to have a Pin<Arc<Mutex<SomeFuture>>>. Or perhaps a Pin<Arc<Mutex<SomeOtherStructure>>> for any structure that happens to use self-references in its implementation. In practice this doesn’t work, because Pin protects DerefMut but not Deref, which is one of the reasons I believe Pin should have been designed differently.

I had this topic recently in some discussion. AFAICT an alternative PinnableMutex that only supports Pin<&PinnableMutex<T>> -> Pin<&mut T>, i. e. there's no &self -> &mut T method, should be sound. Then Pin<Arc<PinnableMutex<SomeFuture>>> is usable with existing APIs for Arc.

Alternatively, Mutex could introduce some additional boolean inner state that, when you first call the Pin<&Mutex<T>> -> Pin<&mut T> method, stores the information that it's now in "pinned mode" which prevents any successful future use of the &Mutex<T> -> &mut T method (e.g. by having it returning an error or panicking).

That's just technically an alternative I could come up with, I don't claim it's a "good" API necessarily.

1 Like

Right, it is missing API because you can't have a Pin<Weak<...>>

I actually made a crate that have a PinWeak type, that fills this hole. The documentation explains it:

A new_cycle function could be added in the crate.

Hmm... :‌(

We wonder where exactly this went wrong? Where would we want to look to figure that out?

This is correct; if you don't expose the ability to get an unpinned mutable reference from a shared reference, the API works. I demonstrated the correct API using RefCell as a model, but the same principle applies to Mutex. GitHub - withoutboats/pin-cell: pin-safe interior mutability

Apparently no one needs to combine pinning with interior mutability in this way in practice, so the APIs have not developed in the ecosystem.

This was a trade off, like so many things: either don't allow getting a shared reference from a pinned reference, and now pinned references cannot call any methods that don't know about pin, even though the vast majority don't use interior mutability, or people who want pinned interior mutability in this way need to use custom concurrency primitives. I'm happy with the choice we made.

2 Likes

Hah, neat timing for your answer. I just finished following the urge to create a pinnable::Mutex myself.

3 Likes

Neat! It occurs to me that possibly the reason this hasn't existed yet is really that people don't know it's possible. Safety requirements of pinning are famously opaque.

But lock_no_pin looks unsound to me: the soundness requirement for interior mutability is that there is no ability to get a mutable reference to the interior without having a mutable reference to the exterior. Otherwise, you can move out of a pinned mutex via lock_no_pin.

The returned guard object only implements Deref, unless the target is Unpin.

2 Likes

Ah, clever! Makes sense

The only reason we looked into Arc and Pin is that we're trying to do some weird stuff with Arc and UnsafeCell: IntrusiveQCellOwner? · Issue #30 · uazu/qcell · GitHub

We don't think Pin is actually necessary for safety and soundness here, but we could be wrong.

Anyway... these are all tangents. What's up with Pin and Arc?

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