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.