DEV Community

Chigozie Oduah
Chigozie Oduah

Posted on

Pinning in Rust

Pinning is a very confusing topic I encountered while programming in Rust. I tried hard to learn it, but the guides, articles, and videos I’ve seen are hard to understand. They usually involve having to know another complex concept in Rust, and that just made me go back and forth between other articles, videos, and guides on those other concepts.

In this article, I’ll filter out all those other concepts and focus solely on pinning. After reading this article, you’ll learn to apply it in your code and understand its use in other codes.

First, what is pinning?

Pinning is an essential feature in Rust. It allows developers to pin an object to a position in memory so that your code can't move it anywhere.

This feature is vital when working with objects that reference other objects that tend to change their position in memory regardless of how you want it. When building data structures like linked lists or working with asynchronous code, this can affect your code and cause undefined behaviors.

How do you pin an object?

Pinning an object is simple. Rust provides a Pin struct that allows you to pin objects. The struct is a part of Rust’s standard library; you can access it via std::pin::Pin.

Let’s visit this example below:

struct MyStruct {
    value: u32
}

fn main() {
    let my_struct = MyStruct{ value: 10 };
    println!("{}", my_struct.value);
}
Enter fullscreen mode Exit fullscreen mode

This example contains a simple struct that we use in the main function. In main, we created an instance of the struct with value as 10, and then we print that value to the console.

We can pin my_struct with Box::pin(). As we’ll do in the code block below.

use std::pin::Pin;

struct MyStruct {
    value: u32,
    _pin: PhantomPinned,
}

fn main() {
    let mut my_struct: Pin<Box<MyStruct>> = Box::pin(MyStruct {
        value: 10,
        _pin: PhantomPinned,
    });
    println!("{}", my_struct.value);
}
Enter fullscreen mode Exit fullscreen mode

I haven’t discussed Box, so let’s look into it. Box is a struct that allows you to allocate memory in the heap memory.

Rust has two types of memory that it uses for storing values in your code: stack and heap memory. Rust stores data of predetermined size in the stack and data whose sizes are determined at runtime in the heap. Stack memory has a limited storage ability but is faster than heap memory. But heap memory is more extensive and flexible than stack memory.

Box::pin() allocates memory in the heap and pins it in place.

The following list contains the things you need to know about pinned values.

  • Modifying a pinned object
  • The _pin field
  • What you risk by using pinned objects

Let’s take a look at all of these in detail!

Changing data on a pinned object

One of the things you’ll get stuck in immediately after pinning an object is trying to modify data on it. It can be frustrating, but there’s no need to be afraid. There’s a way to do it, but it involves some rule-breaking processes. We’ll be doing those processes in an unsafe block.

Let’s revisit the last example we had in the previous section. To follow along, I’ll paste it below:

use std::pin::Pin;

struct MyStruct {
    value: u32,
    _pin: PhantomPinned,
}

fn main() {
    let mut my_struct: Pin<Box<MyStruct>> = Box::pin(MyStruct {
        value: 10,
        _pin: PhantomPinned,
    });

    println!("{}", my_struct.value);
}
Enter fullscreen mode Exit fullscreen mode

Let’s say we want to change the value of my_struct.value to 32. If we just try my_struct.value = 32, the compiler will generate an error message telling you it won’t work.

To change the value of my_struct.value, there are a few steps that you must follow:

  1. First, collect a mutable reference to the pinned object with Pin::as_mut(&mut my_struct).
  2. Then, use that mutable reference to reference the object stored in the pin with Pin::get_unchecked_mut(mut_ref).
  3. Finally, use the reference to the object to modify the object however you like.

Any modifications you make will now reflect in the pinned object.

Let’s see how the code looks after following these steps.

use std::pin::Pin;

struct MyStruct {
    value: u32,
    _pin: PhantomPinned,
}

fn main() {
    let mut my_struct: Pin<Box<MyStruct>> = Box::pin(MyStruct {
        value: 10,
        _pin: PhantomPinned,
    });

    println!("{}", my_struct.value);

    unsafe {
        let mut_ref: Pin<&mut MyStruct> = Pin::as_mut(&mut my_struct);
        let mut_pinned: &mut MyStruct = Pin::get_unchecked_mut(mut_ref);
        mut_pinned.value = 32;
    }
    println!("{}", my_struct.value);
}
Enter fullscreen mode Exit fullscreen mode

If you run the code, it displays the value of my_struct.value in the terminal before and after modifying it.

The _pin field

If you noticed, we added a _pin field to the struct in the modifications we made to our code. Now, you may ask yourself what it is and what it does. That’s what we’ll cover in this section.

_pin is a field you place in the struct you want to pin. It tells the compiler that the struct should be pinned. You can apply the Box::pin() method to a struct without the _pin field, but it won’t pin it in the memory.

You can test the previous paragraph yourself with this code:

use std::pin::Pin;

struct MyStruct {
    value: u32,
}

fn main() {
    let mut my_struct: Pin<Box<MyStruct>> = Box::pin(MyStruct {
        value: 10,
    });

    println!("{}", my_struct.value);

    my_struct.value = 32;   // without `_pin`, this works without any issue
    println!("{}", my_struct.value);
}
Enter fullscreen mode Exit fullscreen mode

When you apply the _pin field to the struct and initialize it with PhantomPinned, the line 14 (my_struct.value = 32;) becomes invalid.

use std::pin::Pin;

struct MyStruct {
    value: u32,
    _pin: PhantomPinned,
}

fn main() {
    let mut my_struct: Pin<Box<MyStruct>> = Box::pin(MyStruct {
        value: 10,
        _pin: PhantomPinned,
    });

    println!("{}", my_struct.value);

    my_struct.value = 32;   // This will cause a compilation error
    println!("{}", my_struct.value);
}
Enter fullscreen mode Exit fullscreen mode

If you look at the _pin field, you may also be asking why you need to initialize it with PhantomPinned. The answer is simple: PhantomPinned is a type Rust uses to enforce the pinning rules on a struct. You should always apply PhantomPinned to the _pin field of a struct when you want to pin it. PhantomPinned doesn’t hold any value in memory and does nothing other than enforce pinning rules on the struct that you apply it to.

What do you risk by using pinned objects?

One of the biggest problems with pinned objects is safety. Don’t get me wrong, pinning objects before using them in specific applications promotes safety. Now you might be wondering: I just said that their problem is with safety; what going on? The safety issue with pinned objects lies in using them.

You may have noticed that when modifying the pinned object, my_struct, we had to wrap the processes in an unsafe block. Surrounding the expressions had a reason. As it turns out, you must be extremely careful when tampering with a pinned object. If you’re not, you can risk causing undefined behavior and other problems to essential parts of your code.

Our example was simple, so there wasn’t much to risk. But you still need to wrap it in an unsafe block, even as it is.

Conclusion: why pin an object, then?

Now that you’ve gone through all that, the ultimate question remains. Why bother with pinning objects at all? This question is essential because pinning objects will hold no value without answering them.

To answer these questions, I have compiled a short list that states each of the reasons:

  • Pinning an object in Rust ensures that the object remains in a fixed location in memory (vital for asynchronous programming).
  • Pinning can help prevent data races and other concurrency issues from arising when multiple tasks access the same data.
  • Pinning can also help improve performance by reducing the copying and moving of the code when working with asynchronous data.
  • Pinning ensures that certain types of data are always available in memory, even if the computer swaps out other parts of the program.

Top comments (2)

Collapse
 
babalolajnr profile image
Abdulqudduus Babalola

This is a fantastic article. Thanks for sharing.

Collapse
 
wrongbyte profile image
wrongbyte

Amazing article!