Does Pin<P> tell compiler no to move the data?

How compiler ensure the pinned data not to move?

2 Likes

It doesn't, Pin uses unsafe and a very clever API to guarantee that nothing is moved. Pin is a library construct with no special connection to the compiler.

9 Likes

Specifically Pin is used as Pin<Pointer<T>> (for some pointer type), and it's the T that is pinned, not the pointer.

Pin functions by preventing (safe) access to Pointer<T> (at all) or &mut T, only allowing &T (shared, basically read-only) access. You need to use unsafe code to get at &mut T, and promise on pain of UB not to move the T behind the reference.

4 Likes

Also, to add on this, since it is a common misconception of Pin (which I'd qualify as a nice abstraction when used with futures / async / generators, but a very subtle and thus easy to misuse and thus dangerous abstraction elsewhere, that should thus then be avoided): given a Pin<Pointer<T> (more precisely, a Pin<impl Deref<Target = T>>, and when T is not Unpin (since Unpin is a marker trait to opt-out of all of Pin-related API restrictions), then one cannot, without unsafe, get a &mut T. Nothing more! This is an abstraction that can be relied on by unsafe code when:

  • Pointer<T> does indeed involve a level of pointer indirection, so that moving a Pointer<T> does not move its T.

    • Counter-example: Pin<Wrapper<T>>, where Wrapper is defined as:

      struct Wrapper<T>(T);
      
      impl<T> ::core::ops::Deref for Wrapper<T> {
          type Target = T;
      
          fn deref (self: &'_ Wrapper<T>)
            -> &'_ T
          {
              &self.0
          }
      }
      

    More generally, it is fine when Pointer<T> features a "stable deref", so that no matter how many times it is moved, its &'_ <Pointer<T>>::Target always points to the same place.

    In practice, the most usual such Pointers are type Pointer<T> = Box<T> and type Pointer<T> = &mut T;, which are the ones involved in Future-related APIs.

  • T is not Unpin. Classic counter-example: Pin<Box<str>> or things like that: in those cases Pin is as useful as nipples on a breastplate.

  • the unsafe code that relies on these guarantees interacts with T directly.

    • Counter-example: T = Cell<Inner> for some (non-Unpin) Inner type.

      Indeed, the natural non-Mut Deref that transparently ignores Pin allows one to get &Cell<Inner>, which means that one can then use Cell::swap to move the Inner value.

      More generally, in order to be able to say that if type T = SomeWrapper<Inner>, then by "pinning" T you'd like to also have the property of having "pinned" the Inner value, you need for SomeWrapper to provide a "structural pinning projection". This has historically been one of the most subtle parts of this design, but nowadays the crate pin_project - Rust allows to write these properties in a compile-time checked fashion (i.e., the common mistakes when doing so are caught at compile-time by the crate-provided attribute( macro)s).

5 Likes

The Pin itself could be moved, right?

Yes, the Pin can be moved, but this is not a problem because a Pin should always wrap a reference or pointer, and moving a reference/pointer does not move the thing it points at.

3 Likes

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.