🧭 目录

  1. 引言:为何需要 Pin?

  2. Rust 的内存移动模型与问题根源

  3. Pin<T> 的设计理念

  4. Unpin 特征的角色与自动推导

  5. 实践:实现自定义 Future 的固定内存安全

  6. 进阶:自定义类型的 Pin 安全封装

  7. 总结与思考


1. 引言:为何需要 Pin?

在 Rust 的所有权模型下,变量默认可以安全地被移动(move)。这为性能和灵活性带来了极大好处,但在某些场景下却可能破坏内存安全。

例如:在异步编程(async/await)中,编译器会将 async fn 编译为状态机。状态机内部的局部变量可能引用自身的字段。如果状态机(即 Future)在执行过程中被移动,这些引用将变为悬垂引用(dangling reference),从而导致未定义行为。

Rust 引入了 Pin,用于防止被移动(prevent moving),从而保证引用的内存地址在生命周期内保持稳定。


2. Rust 的内存移动模型与问题根源

在 Rust 中,大多数类型都是 “可移动的”,即:

let a = SomeStruct { field: 42 };
let b = a; // move

这会将 a 的所有权转移到 b,并可能导致 a 的原始内存被释放或覆盖。

然而,对于一些类型(如生成器、Future、自引用结构体),移动会造成引用失效。如下伪代码所示:

struct SelfRef {
    value: String,
    ptr: *const String,
}

let mut s = SelfRef { value: "hello".into(), ptr: std::ptr::null() };
s.ptr = &s.value;
let s2 = s; // ⚠️ 移动后,s.ptr 仍指向旧地址!

此时,s.ptr 成为了悬垂指针。


3. Pin<T> 的设计理念

Pin<T> 是一种 包裹类型(wrapper type),用于禁止对内部数据的移动。

核心思想:

  • 当一个值被“固定(pinned)”后,它的内存地址将保持稳定;

  • Pin 通过类型系统约束,防止对内部值进行可变借用并移动。

Pin 主要通过两种方式使用:

use std::pin::Pin;

fn main() {
    let mut x = 10;
    let mut pinned = Pin::new(&mut x); // 将引用固定
}

Pin 保证:

  • 不能通过 &mut T 移动 T

  • 但可以通过 Pin<&mut T> 安全访问;

  • 若类型实现了 Unpin,则说明它在逻辑上可移动,不需要固定。


4. Unpin 特征的角色与自动推导

Unpin 是一个标记(marker trait),表示某类型的值即使被固定,也依然可以安全移动。

  • 大部分类型默认是 Unpin

  • 自引用类型(如 Future)则 不是 Unpin

  • !Unpin 类型必须放在堆上,通过 Pin<Box<T>>Pin<&mut T> 固定。

例如:

struct MyStruct;
impl Unpin for MyStruct {} // 明确声明可移动

Rust 编译器会自动为安全类型推导 Unpin 实现。


5. 实践:实现自定义 Future 的固定内存安全

我们来构建一个需要固定内存的 Future

use std::pin::Pin;
use std::future::Future;
use std::task::{Context, Poll};

struct MyFuture {
    value: i32,
    ptr: *const i32,
}

impl Future for MyFuture {
    type Output = i32;

    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.get_ref();
        unsafe {
            println!("Value via ptr = {}", *this.ptr);
        }
        Poll::Ready(this.value)
    }
}

MyFuture 被移动,其内部的 ptr 将指向无效地址。通过 Pin<Box<MyFuture>>,我们可以确保其内存位置固定,从而避免潜在的未定义行为。


6. 进阶:自定义类型的 Pin 安全封装

如果我们实现一个自引用结构体,需要自己定义固定规则:

use std::pin::Pin;

struct SelfRef {
    data: String,
    ptr: *const String,
}

impl SelfRef {
    fn init(self: Pin<&mut Self>) {
        let this = unsafe { self.get_unchecked_mut() };
        this.ptr = &this.data;
    }
}

关键点:

  • get_unchecked_mut()unsafe 的;

  • 必须保证在调用后,SelfRef 不再被移动;

  • 否则会破坏 Pin 的内存安全约束。


7. 总结与思考

PinUnpin 的设计是 Rust 内存模型的关键防线之一
它不仅保证了异步任务、生成器、自引用结构的内存稳定性,也提供了更强的类型系统安全。

要点总结:

  • Pin 保证固定内存地址;

  • Unpin 表示类型可安全移动;

  • 异步与生成器场景中大量使用;

  • !Unpin 类型的移动需严格控制;

  • 在自定义 Pin 类型时,需谨慎使用 unsafe


🧠 经验建议:

  • 在写 Future 时,优先使用 Pin<Box<T>>

  • 仅在需要自引用或内存稳定时使用 Pin

  • 避免滥用 unsafe,并阅读 std::pin 文档理解行为语义。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐