New crate: pin-cell

Today I realized a new crate called pin-cell. This crate contains a type called PinCell, which is a kind of cell that is similar to RefCell, but only can allow pinned mutable references into its interior. Right now, the crate is nightly only and no-std compatible.

How is the API of PinCell different from RefCell?

When you call borrow_mut on a RefCell, you get a type back that implements DerefMut, allowing you to mutate the interior value. This is quite powerful: once you have a mutable reference into the RefCell, you can not only mutate the value inside, but move it out of it (as long as you replace it with a valid replacement).

PinCell is not quite so powerful: its borrow_mut returns a type that does not implement DerefMut, and instead only allows you to get a Pin<&mut T> to the interior value. This pinned mutable reference guarantees that you cannot move the interior value, only mutate it in place. This is still powerful enough for methods like Future::poll, but not for functions like mem::swap.

Here’s an example of using the PinCell API to poll a future using interior mutability:

let cell: PinCell<impl Future>;

let mut ref_mut: pin_cell::PinMut<impl Future> = cell.borrow_mut();
let pin: Pin<&mut impl Future> = cell.as_mut();

// Poll the future:
pin.poll(cx);

Why is PinCell useful? What is “pin projection”?

PinCell is useful because it is not safe to get a pinned reference into a RefCell. This operation - getting a pinned reference into something - is called pin projection, and is the aspect of pinning about which one must be most careful.

The term “projection” refers, in the abstract, to the operation of going from an object to an other object contained within that object - for example, going from a struct to one if its fields, or a vector to one of its elements. In Rust, one can project under different “modes” - one can project by value, or by reference, or by mutable reference, for example. And there are restrictions on when each of these projections is allowed.

“Pin projection” is projecting from a pinned reference to an object to a pinned reference to something contained within that object. For pin projection from one type (called T) to another type (called U) to be safe, there are a few requirements:

The first requirement is that (T: Unpin) implies that (U: Unpin). That is, if T ever implements Unpin when U does not, pin projecting from a value of T to a value of U would be unsafe. This is because its possible that if T is Unpin and U is not, you could possibly move U by moving T. RefCell meets this requirement, so that’s not the problem here.

The second requirement is that the Drop implementation of T never moves the value of U you are trying to project to. If the Drop implementation does move the value of U, that is violating the guarantee of Pin that you will never move that value again. Other than checking that there is no Drop impl, we can’t automatically check for this, which is one of the reasons pin projection requires users to write unsafe code. However, RefCell meets this requirement to, so that’s not the problem.

The final requirement - and the one RefCell violates, is that you cannot project from an &T to an &mut U. Pins allow you to get immutable access to the value they are pinning, because (without interior mutability) you cannot possibly move out of an immutable reference. The entire point of RefCell is that it gives you a mutable reference to the value it wraps, out of which you could easily move.

And so, as a result of this, you cannot pin project through a RefCell. This is why there’s value in an additional PinCell type, which is safe to project through (because it never gives you an unpinned mutable reference to the interior value from an immutable reference).

Are there similar APIs that need duplication with pinning?

Yes, the thread-safe analogs of RefCell are similarly unsafe to pin project through, and users would need a “pin-safe” analog. The types I’m refering to are types like Mutex and RwLock.

It’s also worth considering creating pin-safe versions of the lock free collections, which would necessarily be “grow only” collections - you can put things into the collection, but never take them out (because that moves the things you take out). These APIs are not in the standard library though.

Future of this crate

The crate is currently released on the 0.1.X version line. I intend to take it to 1.0.0 once the pin APIs and arbitrary self types (the nightly features it relies on) are stable. There are some known gaps in its API in comparison to RefCell, I’d be glad to accept PRs if anyone is interested in implementing them, and I’ve opened some issues with comments.