I like the monad comparison. Makes a lot of sense that Clconcrete examples of what it is used for make Pin
more approachable than an abstract description, and the same is true for monads. Note that self referencing data is not the only example. There is also the idea of supporting intrusive data structure that is mentioned in the docs to motivate the Drop
guarantee that wouldn't be necessary for the use case of only self referencing data structures. IIRC, intrusive data structures are also actually used for more efficiently implementing certain features in some async runtimes, so this is more than a theoretical or future compatibility concern.
Yeah, Pin
has very confusing documentation (like most of async stuff). It also took me a long time to understand what it really means.
A few things to note. First, every owned value in Rust is always moveable. It is impossible to design a non-moveable type. Rust moves stuff all over the place: everything which logically "moves ownership" also corresponds to physically moving data in memory. Most of those moves are optimized away, but they are important for semantics.
This means that if you want some data to stay in place, you must put it behind some pointer and never provide an API to get by-value access. But all original pointers in Rust were designed for moveability, so you can't use them directly. Exception: raw pointers, but those are very unsafe. Rust needed an intermediate solution: safer than raw pointers (e.g. at least providing liveness and aliasing guarantees), but different from the standard &T
, &mut T
, Box<T>
, Rc<T>
etc.
The chosen solution was to create a wrapper type Pin<P>
, which would simply wrap some pointer type and make it not accessible directly. This means that you could now design special-purpose APIs around Pin<P>
without bothering too much about the capabilities provided by the wrapped pointer. Just don't allow to safely access the pointer or its pointee --- and you're set.
This blog post helped me a lot in understanding Pin/Unpin
: