The Pin trait

From Rust Community Wiki

PinThis links to official Rust documentation is a standard library type that wraps pointer types and marks the data underneath as immovable.

History[edit]

Safe support for self-referential data types has been a topic of discussion for Rust since before version 1.0 was released. There were essentially two major kinds of proposals for handling this, both of which caused their own problems.

C++ addresses this with move constructors, which can be used to create immovable types by marking the move constructor private [1]. Unfortunately, a naive transfer of C++ move constructors to Rust would make writing sound unsafe code much more complicated, because it would allow even the = assignment operator to unwind (an exception in C++ language, a panic in Rust language).

The other option was to add immovable types, which can be used to create (explicit) move constructors if, like in C++, you allow moved-from data types to have an empty representation instead of becoming inaccessible [2]. Unfortunately, if immovable types were directly added to Rust, it would be impossible to write constructor-like functions for them, since returning from the function is a form of move. To resolve this problem, serious proposals for "immovable" types quickly converged on an approach where types were initially movable, and only become immovable when they are in some way "frozen" [3].

The Pin type is a standard library implementation of immovable types. It was originally designed to require no language support at all [4], but is currently a lang type anyway as a hack (see "Safety and Soundness" below.

The Pin API[edit]

The Pin API is centered around std::pin::Pin, a transparent struct wrapper that should be used with pointer types. For example, Pin<&mut usize> makes sense, but Pin<usize> does not. When a pointer like Pin<&mut usize> exists, the underlying usize is said to be pinned.

The Pin API is designed to allow self-referential data to work. To this end, the API prevents you from turning a Pin<&mut SelfReferentialData> back into a &mut SelfReferentialData again, and data types that are designed to use Pin should expose methods that: accept a Pin, allow mutating the type in safe ways, but do not break any self-referential data that the type contains. In particular, standard library functions and language features that can move data around through a pointer, such as mem::swap, don't work with Pin pointers, making it impossible to move the pinned data structure (which is why it's called "pinning").

The Unpin trait[edit]

Most types implement std::marker::Unpin, an automatic trait a lot like std::marker::Send or Sync. When it is implemented, turning Pin<T> into T is allowed; the Unpin trait essentially turns Pin into a no-op. In other words, once you pin something, you can never use it unpinned ever again, unless it implements Unpin.

Typically, if a type does not implement Unpin, it's because the type contains self-referential data.

Pin projection[edit]

Accessing the fields of a pinned type may be completely fine, as long as this doesn't make it possible to break self-referential pointers in the pinned data structure. When a data structure exposes methods to directly access data inside, that structure is said to support pin projection.

To allow safe pin projection, two crates Cargo vec.svgpin-project and Cargo vec.svgpin-project-lite are usually used.

Safety and Soundness[edit]

Pinned, mutable data is not currently consistent with stacked borrows [5]. To work around the problems caused by this, the stacked borrows reference implementation makes a special exception for any struct that does not implement Unpin [6]. This also causes problems with LLVM noalias [7].

See also[edit]

References[edit]