Rust 默认语义是:值可以被 move(按位搬迁)。在大多数类型上这是安全的,但存在一类特殊结构:自引用结构(self-referential struct)
data被移动到了新地址ptr仍然指向旧地址 ❌(悬垂指针)
rust
struct Bad {
data: String,
ptr: *const String,
}
let mut x = Bad {
data: "hello".into(),
ptr: std::ptr::null(),
};
x.ptr = &x.data;
let y = x; // 内存搬迁
Rust 没有 GC,也不跟踪引用修复,因此一旦对象进入"自引用状态",就必须保证它的地址不再变化,这就是 Pin 。
若是没有 Pin,则很容易出现未定义行为(Undefined Behavior, UB)

Pin
Pin 是一个智能指针包装器; Pin 承诺:T指向的数据不会被移动;即程序必须确保 T 的析构器运行前,该引用没有被移:
- 类型系统约束:
Pin禁止获取&mut T; - 不允许: move(包括 mem::swap / replace)
- 允许: 修改字段内容(只要不 move); 读写数据
- unsafe 边界:若绕过 Pin 的约束,必须自己保证"不移动"
Box::Pin
Box::pin 将一个值放入 Box 中,并得到 Pin<Box<T>>。只要 T: 是!Unpin,Pin<Box<T>> 就会阻止从 Box 中移出 T。
rust
use std::marker::PhantomPinned;
struct SelfRef {
data: String,
ptr: *const String,
// PhantomPinned 是一个零大小类型,它不实现 Unpin
// 任何包含 PhantomPinned 的结构体都会变成 !Unpin
_pin: PhantomPinned,
}
impl SelfRef {
fn new(s: String) -> Self {
SelfRef {
data: s,
ptr: std::ptr::null(),
_pin: PhantomPinned,
}
}
fn init(&mut self) {
self.ptr = &self.data as *const String;
}
fn get_ptr(&self) -> &String {
unsafe { &*self.ptr }
}
}
fn main() {
let mut pinned = Box::pin(SelfRef::new("hello".to_string()));
// 使用 get_unchecked_mut()获取!Unpin的引用
unsafe {
pinned.as_mut().get_unchecked_mut().init();
}
println!("{}", pinned.get_ptr());
}
上面定义了一个自引用结构体 (self-referential struct);如果这个结构体被 移动 (move)到新的内存地址:
- data 会在新地址
- 但 ptr 仍然指向 旧地址
- 访问 ptr 会导致 未定义行为 (use-after-free)
Pin 的关键在于:
- 一旦值被 Pin 包裹,就无法安全地获得它的可变引用( &mut T )
- 没有可变引用,就无法移动这个值(移动需要 &mut T )
- 这样就保证了值的内存地址稳定
rust
┌─────────────────────────────────────────────────────────────┐
│ 内存安全保证 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Box::pin() 分配堆内存,值被钉住 │
│ │ │
│ ▼ │
│ 2. PhantomPinned 标记类型为 !Unpin │
│ │ │
│ ▼ │
│ 3. 编译器阻止通过安全代码获取 &mut T │
│ │ │
│ ▼ │
│ 4. 必须用 unsafe { get_unchecked_mut() } 才能修改 │
│ │ │
│ ▼ │
│ 5. 程序员承诺:只初始化,不移动 │
│ │ │
│ ▼ │
│ 6. 自引用指针永远有效 │
│ │
└─────────────────────────────────────────────────────────────┘
Pin!
pin! 宏或手动 Pin<&mut T> 将栈上的值钉住。
一旦用 pin! 钉住一个栈上变量,就不能再通过原变量名移动它,因为编译器会禁止对已经被钉住的变量进行移动操作。
ini
use std::pin::pin;
let value = SelfRef::new("stack".to_string());
let mut pinned = pin!(value);
unsafe {
pinned.as_mut().get_unchecked_mut().init();
}
println!("{}", pinned.get_ptr());
常见使用场景
异步编程与 Future 状态机
Rust 引入 Pin 的直接动力是:async/await 语法会被编译器生成为一个复杂的枚举状态机。
在 async 块中定义一个局部变量,并跨越 await 点引用它时,编译器生成的 Future 结构体会包含一个指向自身内部字段的指针
csharp
async fn example() {
let mut buffer = [0u8; 1024];
let mut reader = MyReader::new(&mut buffer); // reader 持有对 buffer 的引用
reader.read().await; // 挂起,此时 buffer 和 reader 都在 Future 结构体里
}
FFI 与 C 库交互
许多 C 库(如 libuv 或底层内核驱动)要求提供一个指向结构的指针,并由 C 库长期持有。
如果 Rust 端的对象因为作用域结束、重新分配或闭包捕获而被移动,C 库手中的指针就变成了悬空指针 (Dangling Pointer)。通过 Pin<Box<T>> 暴露给 C 接口,可以从类型系统层面承诺该内存在被 Drop 前不会变动。

Unpin
Unpin 为标准库中的一个自动 trait(auto trait),默认"可移动"标记
- 几乎所有类型默认都是 Unpin。
- 只有显式选择不实现
Unpin的类型才是!Unpin。 - 如果
T: Unpin,那么Pin<'a, T>完全等价于&'a mut T;意味着这个类型被移走也没关系,就算已经被固定了,即Pin对这样的类型毫无影响。
| 类型 | 是否可移动 |
|---|---|
T: Unpin |
✅ 可以 move |
T: !Unpin |
❌ 一旦被 Pin,就不能 move |
!Unpin
固定 !Unpin 类型到堆上,能给数据一个稳定的地址,指向的数据不会在被固定之后被移动走:
- 如果
T: !Unpin, 获取已经被固定的 T 类型实例&mut T需要 unsafe。 - 对于
T: !Unpin的被固定数据,必须保证数据内存的有效性从固定时起直到释放。
PhantomPinned 会自动让类型变成 !Unpin
rust
use std::marker::PhantomPinned;
struct MyType {
data: String,
_pin: PhantomPinned,
}