Can Pin<Rc<RefCell<T>>> violate the pin gurantee?

Since RefCell<T> allows for interior mutability, then obtaining a mut &T using RefCell::borrow_mut(), we can do a move via mem::replace(), and that too in (apparently) safe code; whereas documentation for Pin<T> states, that it isn't possible for safe code to obtain a reference to &mut T.

Intutively, it appears that it violates the pin gurantee; or, am I wrong here?

In Pin<Rc<RefCell<T>>> the pinned value is the RefCell<T>, not the T, so the pin guarantee doesn't apply to it. It would apply if RefCell<T> considered the T it contains to be structurally pinned, but it does not because that would be unsound.

3 Likes

I had once written down concretely what a Mutex (same idea as RefCell, of course) API surface could look like if it wanted to actually consider its contained value structurally pinned. This is the API I could come up with, if you’re interested :wink:

It’s a rare use-case where a Pin<&T>-type actually ends up being useful. I also added an API method that can lock with &Self instead of Pin<&Self>-access to the Mutex, but it will not give you mutable access to the value (unless it’s Unpin), only immutable access.

6 Likes

I understand, that techincally it would not violate the Pin<T> gurantee. But since semantically it appears to do, then why exactly would it be unsound to consider T structurally pinned?

It would be unsound for RefCell<T> to structurally pin the T, because given an Pin<&mut RefCell<T>> you can obtain an &RefCell<T>, which lets you obtain &mut T. Safe APIs must not hand out bare mutable references to pinned values, so pinning a RefCell<T> must not pin the T.

4 Likes

To you it appears to violate the pinning guarantee because you're intuitively assuming the T to be structurally pinned, but it is not. If T was indeed structurally pinned it would be unsound as you expect

1 Like

One could make a separate “only-pinning RefCell” type, right? The unsound part is that the RefCell can't reliably tell whether it is pinned and thus whether its contents should be taken as pinned too.

1 Like

If I understand correctly, RefCell<T> itself makes no gurantee about pinning at all, simply because it's not aware of the address sensitivity of T, right? The following is my formulation.

Suppose T becomes address-sensitive for a part of its lifecycle, there's no way to notify RefCell<T> so as to forbid safely obtaining a &mut T, therefore it ignores its pinned state altogether, because RefCell<T>'s original goal is to allow dynamic borrow checking, not tracking whether it's inner value is pinned or not.

I believe this would be solved by having borrow_mut take Pin<&Self>

No, the key point is that you cannot have a pinned T in a RefCell<T> in the first place. RefCell can't ignore a pinned state if that didn't exist in the first place

Well, by saying "ignored", I actually meant RefCell<T> isn't aware whether a pinned reference was taken to the value, because it's not pinned within RefCell<T> itself.

If by "value" you mean the T then there's no way to get a pinned reference to it, because RefCell does not provide a way to do so. It doesn't need to be aware of that because it doesn't let that happen in the first place.

1 Like

Yes, but then you wouldn’t be able to use a RefCell without pinning it. As I said above: one could make a separate “only-pinning RefCell ” type — but you can't have a single RefCell type that serves both purposes, because it is possible to get &T from Pin<&T>.

— Actually, an always-pin-projecting RefCell<T> could still be used unpinned with T: Unpin, but it would still add some inconvenience to use (calling Pin::new() all the time) so it makes sense to keep them separate for ergonomics.

1 Like

Or, it may resemble Cow<T> by conditionally projecting its pin.