bigywater.tech
Rust · 106

Pin 与 Unpin

Pin 常被说成「难懂」。这篇不讲抽象定义,而是先让一个自引用结构体真的被移动成悬空指针,再验证 Pin 怎样保住地址、哪些类型需要 Pin、编译器怎么拦住对 !Unpin 类型的 &mut。

false
自引用结构体被移动后 ptr == &data 的结果
true
Pin> 被移动两次后数据地址不变
E0277
Pin::new / get_mut 用于 !Unpin 类型的错误码

为什么需要 Pin

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    创建后:data 在 0x16b3426e8,ptr 指向 0x16b3426e8,二者相同: true
    移动后:data 在 0x16b342788,ptr 仍指向 0x16b3426e8,二者相同: false

自引用的 Future 来自 async 状态机,见async/await 状态机。

Pin>

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    Pin<Box<Data>> 被移动了两次,Data 的地址不变: true

!Unpin 与 &mut

实测 · 本机执行输出
  源码:
      1 | use std::{pin::Pin, marker::PhantomPinned};
      2 | struct NoMove { v: u32, _p: PhantomPinned }
      3 | fn main() {
      4 |     let mut p = Box::pin(NoMove { v: 1, _p: PhantomPinned });
      5 |     let r: &mut NoMove = p.as_mut().get_mut();
      6 |     r.v = 2;
      7 | }
  编译:失败
    warning: unused import: `pin::Pin`
     --> <实验目录>/src/u106c.rs:1:11
      |
    1 | use std::{pin::Pin, marker::PhantomPinned};
      |           ^^^^^^^^
      |
      = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
    
    error[E0277]: `PhantomPinned` cannot be unpinned
        --> <实验目录>/src/u106c.rs:5:37
         |
       5 |     let r: &mut NoMove = p.as_mut().get_mut();
         |                                     ^^^^^^^ within `NoMove`, the trait `Unpin` is not implemented for `PhantomPinned`
         |
         = note: consider using the `pin!` macro
                 consider using `Box::pin` if you need to access the pinned value outside of the current scope
    note: required because it appears within the type `NoMove`
        --> <实验目录>/src/u106c.rs:2:8
         |
       2 | struct NoMove { v: u32, _p: PhantomPinned }
         |        ^^^^^^
    note: required by a bound in `Pin::<&'a mut T>::get_mut`
        --> <rust 标准库源码>/library/core/src/pin.rs:1598:12
         |
    1596 |     pub const fn get_mut(self) -> &'a mut T
         |                  ------- required by a bound in this associated function
    1597 |     where
    1598 |         T: Unpin,
         |            ^^^^^ required by this bound in `Pin::<&mut T>::get_mut`
    
    …(共 33 行)
  源码:
      1 | use std::{pin::Pin, marker::PhantomPinned};
      2 | struct NoMove { v: u32, _p: PhantomPinned }
      3 | fn main() { let x = NoMove { v: 1, _p: PhantomPinned }; let p = Pin::new(&x); let _ = p; }
  编译:失败
    error[E0277]: `PhantomPinned` cannot be unpinned
        --> <实验目录>/src/u106d.rs:3:74
         |
       3 | fn main() { let x = NoMove { v: 1, _p: PhantomPinned }; let p = Pin::new(&x); let _ = p; }
         |                                                                 -------- ^^ within `NoMove`, the trait `Unpin` is not implemented for `PhantomPinned`
         |                                                                 |
         |                                                                 required by a bound introduced by this call
         |
         = note: consider using the `pin!` macro
                 consider using `Box::pin` if you need to access the pinned value outside of the current scope
    note: required because it appears within the type `NoMove`
        --> <实验目录>/src/u106d.rs:2:8
         |
       2 | struct NoMove { v: u32, _p: PhantomPinned }
         |        ^^^^^^
    note: required by a bound in `Pin::<Ptr>::new`
        --> <rust 标准库源码>/library/core/src/pin.rs:1159:25
         |
    1159 | impl<Ptr: Deref<Target: Unpin>> Pin<Ptr> {
         |                         ^^^^^ required by this bound in `Pin::<Ptr>::new`
    ...
    1181 |     pub const fn new(pointer: Ptr) -> Pin<Ptr> {
         |                  --- required by a bound in this associated function
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.

哪些类型是 Unpin

实测 · 本机执行输出
  i32                              Unpin: ✓
  String                           Unpin: ✓
  Vec<u8>                          Unpin: ✓
  Box<T>                           Unpin: ✓
  &mut i32                         Unpin: ✓
  PhantomPinned                    Unpin: ✗
  含 PhantomPinned 的 struct         Unpin: ✗
  async fn 的 Future(跨 await 借用)    Unpin: ✗
  async 块(无借用)                     Unpin: ✗

栈上固定

实测 · 本机执行输出
  编译:成功;警告:
    warning: unused import: `Poll`
     --> <实验目录>/src/u106f.rs:1:53
      |
    1 | use std::{pin::pin, future::Future, task::{Context, Poll, Waker}};
      |                                                     ^^^^
      |
      = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
    
    warning: 1 warning emitted
  运行(退出码 0):
    poll 栈上固定的 Future: Ready(6)
  编译:成功
  运行(退出码 0):
    x

Pin 在 poll 里的使用见自定义 Future。

分步演示

Pin 与 Unpin
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:Pin::new_unchecked 的 unsafe 契约、结构投影(pin-project)、Drop 对 pinned 值的保证、Pin<&mut T> 的 as_mut/set 用法、一个 !Unpin 类型被 Pin 之后 mem::swap 的编译错误细节
    京ICP备2026067502号-1京公网安备11010502063487号