Skip to content

Ch 7: std::pin - 固定内存

Pin 是 Rust 中用于处理自引用数据结构的关键类型,解决了在移动后引用指向旧地址的问题。本章将详细讲解 Pin、Unpin trait 和 pin_mut! 宏,帮助你理解 Rust 的内存固定机制。

在 Rust 中,正常情况下值在内存中的位置是固定的:

fn main() {
let x = 42;
let y = x;
// x 被"移动"到 y,但值 42 仍然在同一个内存位置
println!("{}", y);
}

但是,某些类型持有指向自身的引用:

struct SelfRef {
value: i32,
pointer: *const i32, // 指向 value 的指针
}
fn main() {
let mut sr = SelfRef {
value: 42,
pointer: std::ptr::null(),
};
// 设置自引用
sr.pointer = &sr.value;
println!("value: {}, pointer: {}", sr.value, unsafe { *sr.pointer });
// 如果 sr 被移动,pointer 就会指向错误的位置!
}

Pin 将一个类型为 T 的值”固定”到内存的某个位置:

定义:

pub struct Pin<T> {
pointer: T,
}

示例:

use std::pin::Pin;
fn main() {
let mut x = 5;
// Pin 一个可变引用
let pinned_x: Pin<&mut i32> = Pin::new(&mut x);
// Pin::into_inner() 取出内部值
let inner = Pin::into_inner(pinned_x);
println!("{}", inner); // 5
}

Unpin 是一个 auto trait,默认情况下大多数类型都实现了它:

// 简化定义
pub auto trait Unpin {}
// 大多数类型都是 Unpin
impl Unpin for i32 {}
impl Unpin for String {}
impl Unpin for Vec<u8> {}
impl Unpin for &str {}
impl<T> Unpin for Box<T> {}

手动实现 Unpin(不推荐):

use std::pin::Pin;
struct MyType {
data: String,
}
// 显式标记为 !Unpin(但通常不应该这样做)
struct PinnedMyType {
data: String,
_pinned: std::marker::PhantomPinned,
}

Pin 主要用于处理自引用结构体:

use std::pin::Pin;
use std::marker::PhantomPinned;
#[derive(Debug)]
struct SelfReferential {
data: String,
// 使用 PhantomPinned 将类型标记为 !Unpin
_pinned: PhantomPinned,
}
impl SelfReferential {
fn new(data: &str) -> Pin<Box<Self>> {
let mut s = Box::pin(SelfReferential {
data: String::from(data),
_pinned: PhantomPinned,
});
// 错误示范:无法在 safe Rust 中创建真正的自引用
Pin::new(s)
}
}
fn main() {
let mut s = SelfReferential::new("hello");
println!("{:?}", s);
}

Pin::new - 要求 T: Unpin:

use std::pin::Pin;
fn main() {
let mut x = 5;
// Pin::new 要求 x 是 Unpin
let p: Pin<&mut i32> = Pin::new(&mut x);
}

Pin::new_unchecked - 用于 !Unpin 类型:

use std::pin::{Pin, PinMut};
fn main() {
// 使用 unsafe 固定自引用结构体
// 这需要开发者自己保证不移动数据
}

pin_mut! 宏用于方便地固定局部变量:

宏签名:

macro_rules! pin_mut {
($($x:ident),* $(,)?) => {
$(
let mut $x = $x;
// 固定 $x
)*
};
}

示例:

use std::pin::pin_mut;
fn main() {
let mut value = String::from("hello");
// 等价于: let mut pinned: Pin<&mut String> = Pin::new(&mut value);
pin_mut!(value);
// 可以直接调用固定的值
value.push_str(" world");
println!("{}", value);
}

Pin 最重要的应用是 async/await:

use std::pin::Pin;
use std::future::Future;
async fn my_async_fn() {
// 编译器生成的 Future 可能会持有自引用
// 因此 async 函数返回的 Future 被自动 Pin
}
// 手动实现 Future 需要处理 Pin
struct MyFuture {
state: State,
}
impl Future for MyFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// self 被 Pin,无法移动
let this = self.get_mut();
// 使用 get_mut() 需要类型是 Unpin
Poll::Ready(())
}
}

Rc - 不实现 Unpin(对于某些操作):

use std::rc::Rc;
use std::pin::Pin;
fn main() {
let rc: Rc<i32> = Rc::new(42);
// Rc 实现了 Unpin,所以可以 Pin
let pinned_rc: Pin<Rc<i32>> = Pin::new(rc);
}

Box - 总是 Unpin:

use std::pin::Pin;
use std::boxed::Box;
fn main() {
let boxed: Box<i32> = Box::new(42);
// Box<T> 总是 Unpin
fn takes_unpin<T: Unpin>(_: T) {}
takes_unpin(boxed);
}
use std::pin::Pin;
use std::future::Future;
use std::task::{Context, Poll};
struct CounterFuture {
count: u32,
max: u32,
}
impl Future for CounterFuture {
type Output = u32;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// 注意:self 被 Pin,所以我们不能移动 self
// 但我们可以使用 get_mut()(如果类型是 Unpin)
if self.count < self.max {
self.count += 1;
println!("count = {}", self.count);
cx.waker().wake_by_ref();
Poll::Pending
} else {
Poll::Ready(self.count)
}
}
}
fn main() {
// Future 被 Pin 才能被 poll
let future = CounterFuture { count: 0, max: 3 };
// Box::pin 创建的 Future 是自动 Pin 的
use std::future::pin_mut;
pin_mut!(future);
// 手动实现 Future 轮询
// 在实际代码中会使用 executor
}
  1. Pin 本身总是 Unpin:只有当 T 是 !Unpin 时才是 !Unpin。

  2. Unpin 是 auto trait:大多数类型默认实现,只有自引用结构体等特殊情况才是 !Unpin。

  3. Pin 不能阻止所有移动:Pin<&mut T> 或 Pin<&mut T> 本身可以被移动,但 Rust 借检查器会阻止。

  4. PhantomPinned:用于将类型显式标记为 !Unpin,创建自引用结构体。

  5. Pin::get_mut:只有 Unpin 时才能调用,从 Pin 中取出内部值。

std::pin 模块提供了 Rust 内存固定机制:

组件作用
Pin将值固定到内存位置
Unpin标记可以被移动的类型(auto trait)
PhantomPinned将类型标记为不可移动
pin_mut!便捷宏,固定局部变量

Pin 是 Rust 异步编程的基础,正确理解它对于编写高效的异步代码至关重要。