How to understand `Pin` in rust language?

When I was learning the rust language, I ran into an obstacle: the self-referential structure Pin, I checked some information through Google and found that none of the understanding is deep, I just know that Pin can fixed the memory so far, I have some questions like the following:

  1. what is the purpose of pin to solve the problem?
  2. what is the underlying implementation principle and mechanism of pin? Why is it possible to pin (fixed) memory from being moved, and how does it do so?
  3. What is the essential difference between Pin, UnPin and !UnPin?
  4. Is it possible for all data types to be pinned (fixed) and not moved from memory?
  5. How is it handled for heap and stack?
  6. What is the recommendation for using pin in project development?
  7. What is the relationship between Pin and ownership and lifetime?
    ...

Maybe you can checkout this video first.

2 Likes

To make operations which could break the position-dependent invariants unsafe, therefore allowing to expose the value which uses these invariants (such as self-referential value) to safe code.

If the value has any position-dependent invariant (i.e. if it'd be broken by moving), it is not Unpin. When T is not Unpin, it's unsafe to go from Pin<Ref<T>> (where Ref is any unique-reference-like type - &mut T, Box<T>, and so on) to &mut T. Therefore, safe code can't use any operation which would move T (except dropping), since they all need either a bare T or &mut T.

  • Pin is a wrapper type, which restricts the set of possible operations, as described above.
  • Unpin is a marker trait, which lifts the restrictions imposed by Pin. The absence of Unpin, therefore, lets these restrictions stay in place.

It is always possible, but not always meaningful. For example, pinning any kind of Copy types is pointless, since it's always safe to get &T from Pin<Ref<T>>, and Copy types can be simply copied from behind the shared reference.

The only difference is that stack-pinned values can't be returned from function they're pinned in (since the stack frame they're pinned to will be destroyed on return). Otherwise, Pin<&mut T> is much the same as Pin<Box<T>>.

AFAIK, the recommendations are simple:

  • Don't use it if you don't strictly need it (that is, if you don't use it in some async code).
  • Otherwise, use one of Box::pin, FutureExt::boxed or pin_mut, whatever makes sense for your types.
  • And don't reach for unsafe, unless you know exactly why all other variants won't work.

As with any other wrapper - it owns the value it contains and can't outlive this value, if it is a reference.

5 Likes

Just being pedantic. (But maybe helpful to someone to understand how it lifts.)
Traits extend the functionality. It is the where clause of functions this comes in play.
So the starting point is a lack of functionality, (Exception with Sized being on by default and you have to specify it is not required with ?Sized.)
It is true for Unpin but the compiler adds this trait to most things; but keep thinking that you start without it.
Original starting point is one of being unsafe to move.

Now you slightly reverse what is quoted.
Pin lifts the restrictions by adding functions for the cases where the type is Unpin as a guarantee of being safe to move has been added. In absence of guarantee you have to resort to unsafe.

If you only have Pin-wrapped pointers/references to a structure (and the pointed-to type doesn't implement Unpin, and you cannot obtain any &mut reference afterwards), you cannot move the memory pointed-to by the pointer/reference (e.g. by using std::mem::swap), thus any self-referential pointers will remain valid (and not become dangling because the memory addresses would have changed).

This is because in order to move the memory from "safe Rust", you'd need to obtain a mutable reference (which the Pin wrapper prohibits you to get).

Note that storing self-referential (raw) pointers within the structure still requires unsafe Rust when dereferencing these pointers. Thus you cannot create your own self-referential structures without unsafe Rust.

The Pin wrapper doesn't really prohibit moving the memory. What prohibits you from moving the memory is that you don't get a &mut reference.

Pin is a wrapper for pointer-like types, and Unpin is a trait to be implemented on types where it's okay to add and remove the wrapper from pointer-like types to that type at will (without needing to actually pin any memory). !Unpin is a notation to specify that the Unpin trait is not implemented, i.e. if a type is !Unpin it means that wrapping a pointer to that type in a Pin will actually require that the memory becomes pinned. :face_with_spiral_eyes:

I found this in the docs of std::pin:

Moreover, if your type is #[repr(packed)], the compiler will automatically move fields around to be able to drop them. It might even do that for fields that happen to be sufficiently aligned. As a consequence, you cannot use pinning with a #[repr(packed)] type.

Otherwise, you can always add a std::marker::PhantomUnpin to a struct, prohibiting the compiler to automatically implement Unpin for the struct. But in order to later wrap a pointer to the struct in a Pin, you will then need unsafe code and ensure that all the promises of pinning are kept.

Pinning on the heap is usually done with Box::pin (but there's also Arc::pin and others).

Pinning on the stack can be done with one of the following marcros:

If I'm not mistaken, they are all equivalent.

I believe the general recommendation is to avoid self-referential structs. They can be avoided in most cases. When dealing with async code, it may be necessary to pin certain structures before you can use them, e.g. Futures.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.