The last article on Pin was a shallow introduction to what Pin is all about and why it is needed, but it is still not enough to master that part of knowledge, so this article hopes to systematically sort out the knowledge points related to Pin, so I named the title “Rust Pin Advanced”.

Pin API Anatomy

To understand Pin in depth, it is essential to be familiar with all of its methods. Excluding the nightly API, Pin has 13 methods in total.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Pin<P> where P: Deref
impl Pin<P> where P: Deref {
  unsafe fn new_unchecked(pointer: P) -> Pin<P>

  fn as_ref(&self) -> Pin<&P::Target>

  unsafe fn into_inner_unchecked(pin: Pin<P>) -> P
}

impl<P: Deref<Target: Unpin>> Pin<P> {
  fn new(pointer: P) -> Pin<P>

  fn into_inner(pin: Pin<P>) -> P
}

impl<'a, T: ?Sized> Pin<&'a T> {
  unsafe fn map_unchecked<U, F>(self, func: F) -> Pin<&'a U>
    where
        U: ?Sized,
        F: FnOnce(&T) -> &U

  fn get_ref(self) -> &'a T
}


// Pin<P> where P: DerefMut
impl<P: DerefMut> Pin<P> {
  fn as_mut(&mut self) -> Pin<&mut P::Target>

  fn set(&mut self, value: P::Target) where P::Target: Sized
}

impl<'a, T: ?Sized> Pin<&'a mut T> {
  fn into_ref(self) -> Pin<&'a T>

  fn get_mut(self) -> &'a mut T where T: Unpin

  unsafe fn get_unchecked_mut(self) -> &'a mut T

  unsafe fn map_unchecked_mut<U, F>(self, func: F) -> Pin<&'a mut U>
    where
        U: ?Sized,
        F: FnOnce(&mut T) -> &mut U
}

These methods can be divided into two broad categories.

  • Pin<P> where P: Deref
  • Pin<P> where P: DerefMut

As mentioned in the previous article, Pin is generally represented as Pin<P<T>> (P is the abbreviation for Pointer and T is the abbreviation for Type), so the content wrapped in Pin can only be a smart pointer (any type that implements the Deref trait can be called a smart pointer), and has no meaning for other ordinary types. Since &T and &mut T implement Deref and DerefMut respectively, Pin<&'a T> and Pin<&'a mut T> are considered special implementations of these two classes respectively.

At first glance, these 13 methods look a bit haphazard, but they are actually very well designed, to the point of symmetry. By function, these methods can be divided into 5 major categories, each of which is subdivided into 2 to 3 categories according to mutability or compliance with the T: Unpin restriction. Variable versions end in mut, because unsafe versions that do not conform to the T: Unpin restriction contain unchecked.

Functions Methods Remarks
Construct Pin new() / new_unchecked() Distinguish between safe and unsafe versions by whether they satisfy the T: Unpin restriction.
Convert Pin type as_ref() / as_mut() Converts &/&mut Pin<P<T>> to Pin<&/&mut T>.
Get the borrow of T inside P<P<T>> get_ref() / get_mut() / get_unchecked_mut() consume ownership and get the borrow of T inside. There are two versions by mutability. Since &mut T is the “root of all evil”, get_mut also distinguishes between safe and unsafe versions according to whether or not they satisfy the T: Unpin restriction.
Consume Pin ownership and get the pointer inside P into_inner() / into_inner_unchecked() Distinguish between safe and unsafe versions by whether they satisfy the T: Unpin restriction. Also, to avoid conflicts with P’s own into class methods, these APIs are designed as static methods that must be called with Pin::into_inner(), not pin.into_inner().
Pin projection map_unchecked() / map_unchecked_mut() Usually used for Pin projection.

There are only two methods left that are not categorized in the table above, and they are also relatively simple, namely

  • Pin::set() - Sets the new T value in Pin<P<T>>.
  • Pin<&mut Self>::into_ref() - Converts Pin<&mut T> to Pin<&T>.

It is worth noting that the implementation of new() and new_unchecked(), get_mut() and get_unchecked_mut(), into_inner() and into_inner_unchecked() are actually identical, the only difference is that the safe version has the Unpin restriction.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pub const fn new(pointer: P) -> Pin<P> 
  where P: Deref,
         <P as Deref>::Target: Unpin {
    unsafe { Pin::new_unchecked(pointer) }
}

pub const unsafe fn new_unchecked(pointer: P) -> Pin<P> {
        Pin { pointer }
}

pub const fn get_mut(self) -> &'a mut T where T: Unpin {
        self.pointer
}

pub const unsafe fn get_unchecked_mut(self) -> &'a mut T {
        self.pointer
}

pub const fn into_inner(pin: Pin<P>) -> P
    where P: Deref,
            <P as Deref>::Target: Unpin {
    pin.pointer
}

pub const unsafe fn into_inner_unchecked(pin: Pin<P>) -> P {
        pin.pointer
}

Why should there be a distinction between safe and unsafe versions of the same code? To answer this question, we have to go back to the nature of Pin. The essence of Pin is to ensure that the memory address of T in Pin<P<T> is not changed (i.e., not moved) under safe Rust unless T satisfies T: Unpin. The essence of ensuring that the memory address of T is not changed is to avoid exposing T or &mut T (“the root of all evil”). If you expose T, you can just move it; if you expose &mut T, the developer can call methods like std::mem::swap() or std::mem::replace() to move T. Another thing is that the boundary between safe and unsafe in Rust must be very clear and unambiguous. So as long as you don’t satisfy T: Unpin, then any method that needs to construct Pin<P<T>>, expose T or &mut T should be unsafe.

Satisfy T: Unpin Not Satisfy T: Unpin
Construct Pin safe unsafe
Exposure T safe unsafe
Exposure &T safe safe
Exposure &mut T safe unsafe

For example, into_inner_unchecked() returns P, but it is indirectly exposing T and &mut T. Because you can easily get T or &mut T with *P or &mut *P. And you construct Pin<P<T>> as if you were promising to abide by the Pin contract, but this step is clearly a violation of that contract.

Why is Pin::get_ref() safe? Because it returns &T, and there’s no way to move it: the std::mem::swap() class method only supports &mut T, and the compiler will error you if you dereference &T. (Thanks again rustc) Another thing to emphasize is the type of internal mutability. For example, for RefCell<T>, Pin<&mut RefCell<T>>.into_ref().get_ref() returns &RefCell<T>, while methods like RefCell<T>::into_inner() can get T and move it. But that’s okay, because the contract of Pin<P<T>> is to ensure that T inside P is not moved, and here P is &, and T is RefCell, not T inside RefCell<T>. This is fine as long as there is no additional Pin<&T> pointing to T inside RefCell<T>, but you’ve actually eliminated that possibility automatically when you construct RefCell<T>. Because the argument to RefCell::new() is value: T, which already moves T in.

Similarly, Pin<&mut Box<T>> guarantees that Box<T> itself is not moved, not the T inside Box. To ensure that T inside Box<T> is not moved, just use Pin<Box<T>>.

Pin additional attributes

#[fundamental]

Traits marked with the #[fundamental] attribute are not subject to the orphan rule. So you can give Pin<P<T>> impl your local trait.

1
2
3
4
5
6
use std::pin::Pin;

trait LocalTrait {}

impl<P> LocalTrait for Pin<P> {
}

#[repr(transparent)

#[repr(transparent)] This property allows Pin to have the same ABI layout as the pointer field inside, which can be useful in FFI scenarios.

The #[repr(transparent)] attribute is now stable. This attribute allows a Rust newtype wrapper (struct NewType<T>(T); ) to be represented as the inner type across Foreign Function Interface (FFI) boundaries.

Traits implemented by Pin

Let’s take a look at what traits Pin implements that are of interest.

Unpin

1
impl<P> Unpin for Pin<P> where P: Unpin {}

Since Unpin is an auto trait, Pin<P<T> will also achieve Unpin if it satisfies P: Unpin. And almost all Ps will be Unpin, so Pin<P<T>> will almost always be Unpin. This implementation is important, especially if the T in question is a Future. It doesn’t matter if your Future satisfies Unpin or not, after you wrap it in Pin<&mut ... >, it’s a Future that satisfies Unpin (because Pin<P> implements Future, as we’ll see later). Many asynchronous methods may require your Future to satisfy Unpin before they can be called, and the Future returned by the async fn method obviously does not satisfy Unpin, so you often need to pin this Future to it. For example, use the macro tokio::pin!().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use tokio::pin;

async fn my_async_fn() {
    // async logic here
}

#[tokio::main]
async fn main() {
    let future = my_async_fn();
    pin!(future);

    (&mut future).await;
}

Also, it needs to be emphasized again that

  • Pin itself is not Unpin has nothing to do with whether T is Unpin or not, only with P.
  • Pin has nothing to do with whether P is Unpin or not, it has to do with T.

The above two sentences are a bit confusing, but after you figure it out, you won’t be confused about many pin scenarios.

Deref and DerefMut

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
impl<P: Deref> Deref for Pin<P> {
    type Target = P::Target;
    fn deref(&self) -> &P::Target {
        Pin::get_ref(Pin::as_ref(self))
    }
}

impl<P: DerefMut<Target: Unpin>> DerefMut for Pin<P> {
    fn deref_mut(&mut self) -> &mut P::Target {
        Pin::get_mut(Pin::as_mut(self))
    }
}

These two traits are critical to Pin. Only when Deref is implemented is Pin<P> a smart pointer, so that the developer can seamlessly call the methods of P. It is important to note that DerefMut is implemented for Pin<P<T>> only if T: Unpin is satisfied. This is because one of the responsibilities of Pin<P<T>> under Safe Rust is to not expose &mut T without satisfying T: Unpin.

In addition, after implementing these two traits, you can dereference &T and &mut T respectively, but there is a difference between this dereference and get_ref() and get_mut(). Take &T for example, suppose there is let p = Pin::new(&T);, dereference p to get &T: let t = &*p;, here the lifecycle of &T is actually equal to the lifecycle of &Pin::new(&T). And Pin::new(&T).get_ref() gets the lifecycle of &T and the lifecycle of Pin itself are equal.

Why is this the case? Let’s look at the syntactic sugar of dereferenced smart pointers after we expand it.

1
2
3
let p = Pin::new(&T);
// let t = &*p; After expanding the syntactic sugar as follows.
let t = &*Deref::deref(&p);

The code for Pin’s Deref implementation is: Pin::get_ref(Pin::as_ref(self)), while the code for Pin::as_ref() is as follows. By comparison, you can see that the lifecycle of &T obtained by dereferencing is indeed different from that obtained by get_ref().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
impl Pin<P> where P: Deref {
    pub fn as_ref(&self) -> Pin<&P::Target> {
        unsafe { Pin::new_unchecked(&*self.pointer) }
    }
}

// Compare this to the Deref implementation above
impl<'a, T: ?Sized> Pin<&'a T> {
    pub const fn get_ref(self) -> &'a T {
        self.pointer
    }
}

Another thing worth noting is that Pin::as_ref() and Pin::as_mut() will dereference self.pointer, which actually calls its deref() or deref_mut() methods. These two methods are implemented by P itself, so there is a possibility of a “malicious implementation” of T move here. But this “malicious implementation” will be ruled out by Pin’s contract: this is caused by your own “malicious implementation”, not by using Pin.

The documentation for Pin::new_unchecked() makes a point of emphasizing this point. By using this method, you are making a promise about the P::Deref and P::DerefMut implementations, if they exist. Most importantly, they must not move out of their self arguments: Pin::as_mut and Pin::as_ref will call DerefMut::deref_mut and Deref::deref on the pinned pointer and expect these methods to uphold the pinning invariants.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
use std::pin::Pin;
use std::marker::PhantomPinned;
use std::ptr::NonNull;
use std::ops::{Deref, DerefMut};

struct Unmovable {
    data: String,
    slice: NonNull<String>,
    _pin: PhantomPinned,
}

impl Unmovable {
    fn new(data: String) -> Pin<Boz<Self>> {
        let res = Unmovable {
            data,
            slice: NonNull::dangling(),
            _pin: PhantomPinned,
        };
        let mut bozed = unsafe { Pin::new_unchecked(Boz(res)) };

        let slice = NonNull::from(&bozed.data);
        // we know this is safe because modifying a field doesn't move the whole struct
        unsafe {
            let mut_ref: Pin<&mut Self> = Pin::as_mut(&mut bozed);
            Pin::get_unchecked_mut(mut_ref).slice = slice;
        }
        bozed
    }
}

impl Default for Unmovable {
    fn default() -> Self {
        Unmovable {
            data: String::new(),
            slice: NonNull::dangling(),
            _pin: PhantomPinned,
        }
    }
}

struct Boz<T>(T);

impl<T> Deref for Boz<T>  {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

// "Malicious" implementations of DerefMut
impl<T: Default> DerefMut for Boz<T>  {
    fn deref_mut(&mut self) -> &mut Self::Target {
        let _s = std::mem::take(&mut self.0);
        &mut self.0
    }
}

fn main() {
   let mut unmovable = Unmovable::new(String::from("Malicious!!!"));
   unmovable.as_mut();
}

In the above example, we construct a Pin<Boz<Unmovable>>, and then call the as_mut() method to dereference this Boz, which has a “malicious” DerefMut implementation that moves away this Unmovable. But I obviously have it Pin in place.

Future

Pin also implements Future, which is closely related to Unpin, so we’ll cover that in the next section.

Unpin and Future

One of the big things that confuses beginners about Rust’s pinning API is the introduction of Unpin, which can often be confusing, so it’s important to get a thorough understanding of Unpin, and in particular its relationship to Future.

As mentioned before, Unpin is an auto trait, and almost all types implement Unpin, including some types you didn’t realize. For example.

  • &T: impl<'a, T: ?Sized + 'a> Unpin for &'a T {}
  • &mut T: impl<'a, T: ?Sized + 'a> Unpin for &'a mut T {}
  • *const T: impl<T: ?Sized> Unpin for *const T {}
  • *mut T: impl<T: ?Sized> Unpin for *mut T {}
  • Other, including Box, Arc, Rc, etc.

Note that here they are Unpin regardless of whether T satisfies T: Unpin or not. The reason for this has already been stated: The ability of Pin to pin T has nothing to do with whether P is Unpin or not, but only with T.

As mentioned in the previous article, only std::marker::PhatomPinned, which contains the type PhatomPinned, and .await the structure that follows the desyntactic sugar is !Unpin, which is not repeated here.

Unpin is a safe trait

Another important feature: Unpin is a safe trait, which means you can implement Unpin for any type under safe Rust, including your Future type.

We prepare two assert functions in advance, which will be used later.

1
2
fn assert_future<F: Future>(_f: F) {}
fn assert_unpin<T: Unpin>(_t: T) {}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use std::{
    future::Future,
    pin::Pin,
    task::{Context, Poll},
};

#[derive(Clone)]
struct Dummy(String);

impl Future for Dummy {
    type Output = ();

    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
        println!("{}", self.0);
        Poll::Ready(())
    }
}

// Without this line, the compiler will also automatically implement Unpin for you
impl Unpin for Dummy {}

fn main() {
    let dummy = Dummy(String::from("hello world!"));
    assert_future(dummy.clone());
    assert_unpin(dummy);
}

If you want to poll this Dummy future in another Future it’s no problem at all. The futures crate even provides a series of unpin versions of methods to help you do this, such as FutureExt::poll_ unpin().

1
2
3
4
5
6
7
8
9
pub trait FutureExt: Future {
    /// A convenience for calling `Future::poll` on `Unpin` future types.
    fn poll_unpin(&mut self, cx: &mut Context<'_>) -> Poll<Self::Output>
    where
        Self: Unpin,
    {
        Pin::new(self).poll(cx)
    }
}

You can see that this is &mut self, not self: Pin<&mut Self>.

However, the pin projection scenario requires special attention, if you have a field of type !Unpin, you can’t implement Unpin for this type. See the official website Pinning is structural for field for details.

Why Future can be Unpin

Some people may ask, “Wasn’t Pin originally designed to solve the problem of self-referencing structures that don’t get moved when implementing Future? Why is it possible to implement Unpin for the Future type? The reason is this: if you implement Future as a self-referential structure, then of course it can’t be Unpin, but otherwise it’s perfectly fine to implement Unpin. The example above, and many third-party libraries’ Future types, do not have self-referential structs, so you can move with confidence, so it can be Unpin. Another advantage is that you can use the safe version of the Pin::new() method to construct Pin to poll future, without having to deal with unsafe.

Pin’s Future implementation

The reason we moved here to talk about the Future implementation of Pin is that 1.56 has a PR #81363 that removes the P: Unpin restriction. Let’s first look at why we need to implement Future for Pin, and then analyze why the Unpin restriction can be let go here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-   P: Unpin + ops::DerefMut<Target: Future>,
+   P: ops::DerefMut<Target: Future>,
{
    type Output = <<P as ops::Deref>::Target as Future>::Output;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
-       Pin::get_mut(self).as_mut().poll(cx)
+       <P::Target as Future>::poll(self.as_deref_mut(), cx)
        // self.as_deref_mut() is actually unsafe { self.get_unchecked_mut() }.as_mut()
    }

The reason for implementing Future for Pin is simply to make it easier to call poll(), especially in the pin projection scenario. Since self of poll() is of type Pin<&mut Self>, you can’t call poll() directly with future.

1
2
3
4
5
error[E0599]: no method named `poll` found for struct `Dummy` in the current scope
  --> src/main.rs:35:20
   |
35 |         Dummy(String::from("hello world!")).poll(cx)
   |                                             ^^^^ method not found in `Dummy`

You have to construct a Pin<&mut Dummy> before you can call poll(). After implementing Future for Pin, you can just write: Pin::new(&mut dummy).poll(ctx), otherwise you need to write Future::poll(Pin::new(&mut dummy), ctx).

Again, let’s see why P::Unpin is not needed here. First, the purpose of this method is to poll P::Target, a Future, and the Self of the poll() method is Pin<P<T>> and self is Pin<&mut Pin<P<T>>> (note that there are two layers of Pin here). We need to safely convert Pin<&mut Pin<P<T>>> to Pin<&mut T>> in order to call poll() on P::Target. It’s easy to get Pin<&mut T>, there’s Pin::as_mut(), and both versions end up calling as_mut(), so there’s no problem here. But the signature of as_mut() is &mut self, which means we have to get &mut Pin<P<T>> first. If we reduce Pin<&mut Pin<P<T>>> to the basic form Pin<P<T>>, then &mut is the P and Pin<P<T>> is the T. To get &mut Pin<P<T>>> from Pin<&mut Pin<P<T>> is actually to get &mut T from Pin<P<T>>. Both get_mut() and get_unchecked_mut() methods are satisfied, the only difference is the Unpin restriction, which is where that PR change comes in. Without the Unpin restriction, we would have to use the unsafe version of get_unchecked_mut(). But it’s completely safe here, because we call as_mut() as soon as we get &mut Pin<P<T>>, and we don’t move it. So the previous P: Unpin is redundant. For more details, see the documentation and source code comments for Pin::as_deref_mut().

Why Unpin constraints are needed

As mentioned above, some asynchronous-related APIs require your type to meet Unpin in order to be called. As far as I can tell, these APIs fall into three general categories.

  1. Scenarios that require &mut future. **For example, tokio::select!(), a macro that requires your Future to satisfy Unpin.
  2. The AsyncRead / AsyncWrite scenario. **For example, the method tokio::io::AsyncWriteExt requires your Self to satisfy Unpin.
  3. Future itself is Unpin compliant and does not want to deal directly with Pin. **The FutureExt::poll_unpin() method mentioned above falls into this category.

Class (2) is mainly related to self of AsyncRead / AsyncWrite which requires Pin<&mut Self>, there are quite a few discussions about this in the community, not the focus of this article, check the following information if you are interested.

Second, tower is also considering whether to add Pin<&mut Self>: Pinning and Service.

Regarding class (1), the main reason is that the implementation of Future for &mut Future specifies the need for F: Unpin.

1
2
3
4
5
6
7
impl<F: ?Sized + Future + Unpin> Future for &mut F {
    type Output = F::Output;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        F::poll(Pin::new(&mut **self), cx)
    }
}

So it comes down to figuring out why we need Unpin here. Let’s start with a scenario where we have a future that we need to keep polling in a loop, but Future::poll() consumes ownership of self every time it is called. So we need to mutably borrow this future to avoid consuming ownership of future. But after &mut future there is a risk of moving the future (“the root of all evil”), so either your future is Unpin or you have to pin it and borrow it mutably (i.e. &mut Pin<&mut future>). And it just so happens that Pin<P> where P: DerefMut implements Future! (as mentioned in the previous section) and Pin<P> also satisfies Unpin! It’s so perfect that we can just implement Future for &mut F, as long as F satisfies Future + Unpin. The advantage of this is that if your future satisfies Unpin, then you can just poll it multiple times in the loop and not worry about the move; if your future doesn’t satisfy Unpin, that’s fine, just pin it. For example, in the following example, because tokio::time::Sleep doesn’t satisfy Unpin, you need to pin it with tokio::pin!() before you can compile it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use tokio::time::{self, Duration, Instant};

#[tokio::main]
async fn main() {
    let sleep = time::sleep(Duration::from_millis(10));
    tokio::pin!(sleep);

    loop {
        tokio::select! {
            () = &mut sleep => {
                println!("timer elapsed");
                sleep.as_mut().reset(Instant::now() + Duration::from_millis(50));
            },
        }
    }
}

By the same token, the implementation of Future for Box<F> also requires Unpin.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
impl<F: ?Sized + Future + Unpin, A: Allocator> Future for Box<F, A>
where
    A: 'static,
{
    type Output = F::Output;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        F::poll(Pin::new(&mut *self), cx)
    }
}

Other scenarios that require Pin

I often encounter people asking questions like “Do I need to use Pin to solve this scenario?” I look at the question and see that it has nothing to do with Pin, so I reply with this classic quote.

Rust Community Quote: Whenever you wonder if Pin could be the solution, it isn’t.

The Pinning API is designed for generality, not just to solve the problem of self-referential struct move in asynchronous, but also for other scenarios where Pin is needed.

Intrusive collections

Intrusive collections is another application scenario for Pin. The documentation for Pin mentions the example of intrusive doubly-linked list, but it is similar for other intrusive data structures (e.g. intrusive single-linked tables). However, the documentation is only a few sentences, which is not very good, so I will briefly summarize it here.

First of all, you need to understand what intrusive collections are. Almost all the data structures we use in collections are non-intrusive, such as the standard library Vec, LinkedList and so on. The characteristic of non-intrusive type collections is that the elements in the collection are completely decoupled from the collection itself, the collection does not need to care what type each element is, and the collection can be used to hold elements of any type. However, a collection of type intrusive is a completely intrusive collection, where the prev or next pointer is defined on top of the element.

Using C++ as an example, a non-intrusive doubly linked list can be defined like this

1
2
3
4
5
6
7
8
struct Point {
    float x, y;
};

struct ListNode {
    Point val;
    ListNode *next, *prev;
};

And the intrusive version needs to be written like this

1
2
3
4
struct Point {
    float x, y;
    Point *next, *prev;
};

The pseudo-code for the Rust version of intrusive would probably also look like this.

1
2
3
4
5
6
struct Point {
    x: f64,
    y: f64,
    prev: Option<Rc<Point>>,
    next: Option<Rc<Point>>,
}

You can see that the biggest difference between the two is whether the pointer is placed on top of the collection or on top of the element. The two types of collections have their own advantages and disadvantages, while the intrusive type has the advantage of better performance and the disadvantage of not being generic and requiring repeated definitions of collections for different elements. Related knowledge is not the focus of this article, for more details you can take a look at the following information.

So why do intrusive collections need to use pins? The reason is that elements have a prev or next pointer to each other, so if one element in the middle moves, the pointer address of the other elements to it will be invalid, resulting in unsafe behavior. Rust has a library called intrusive-collections that provides many intrusive collection types, and Tokio also defines intrusive collections, and no doubt they all use pins.

Other

In fact, as long as we need to deal with the scenario of preventing being moved, theoretically we need to use Pin to solve it. I can’t think of any other cases for now, so I’ll add them later if I find any new ones, or if you know of others, please let me know.

Summary

This article is a little long, so let’s summarize.

  • The API for Pin is very well designed, even full of symmetry, and its methods can be roughly divided into 5 categories. It involves Unpin and &mut T which can be subdivided into safe and unsafe.
  • #[fundamental] and #[repr(transparent)] of Pin are important, but you generally don’t need to care about it.
  • The traits implemented by Pin need to focus on Unpin, Deref / DerefMut and Future, and understanding them will allow you to fully master Pin.
  • Unpin and Future are very closely related. Unpin is a safe trait that can theoretically be implemented arbitrarily, and Future can also be Unpin. Some asynchronous APIs may require Unpin restrictions, and the reason for it needs to be understood, not just used.
  • Pin is a generic API, and there will be other scenarios that require Pin in addition to async / await, such as intrusive collections.

The Pin projection, mentioned several times in the article, is not expanded, so we will discuss it in detail in the next article. See you soon!