Ch 7: std::pin - 固定内存
Pin 是 Rust 中用于处理自引用数据结构的关键类型,解决了在移动后引用指向旧地址的问题。本章将详细讲解 Pin、Unpin trait 和 pin_mut! 宏,帮助你理解 Rust 的内存固定机制。
2. 为什么需要 Pin
Section titled “2. 为什么需要 Pin”在 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 就会指向错误的位置!}3. Pin - 固定值到内存
Section titled “3. Pin - 固定值到内存”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}4. Unpin trait - 可以取消固定
Section titled “4. Unpin trait - 可以取消固定”Unpin 是一个 auto trait,默认情况下大多数类型都实现了它:
// 简化定义pub auto trait Unpin {}
// 大多数类型都是 Unpinimpl 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,}5. Pin 与自引用结构体
Section titled “5. Pin 与自引用结构体”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);}6. Pin::new 与 Pin::new_unchecked
Section titled “6. Pin::new 与 Pin::new_unchecked”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 固定自引用结构体 // 这需要开发者自己保证不移动数据}7. pin_mut! 宏
Section titled “7. pin_mut! 宏”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);}8. Pin 在 async/await 中的应用
Section titled “8. Pin 在 async/await 中的应用”Pin 最重要的应用是 async/await:
use std::pin::Pin;use std::future::Future;
async fn my_async_fn() { // 编译器生成的 Future 可能会持有自引用 // 因此 async 函数返回的 Future 被自动 Pin}
// 手动实现 Future 需要处理 Pinstruct 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(()) }}9. Pin 与标准库类型
Section titled “9. Pin 与标准库类型”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);}10. 实战示例:固定 Future
Section titled “10. 实战示例:固定 Future”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}11. 需要注意的点
Section titled “11. 需要注意的点”-
Pin本身总是Unpin:只有当T是!Unpin时才是!Unpin。 -
Unpin是 auto trait:大多数类型默认实现,只有自引用结构体等特殊情况才是!Unpin。 -
Pin不能阻止所有移动:Pin<&mut T>或Pin<&mut T>本身可以被移动,但 Rust 借检查器会阻止。 -
PhantomPinned:用于将类型显式标记为!Unpin,创建自引用结构体。 -
Pin::get_mut:只有Unpin时才能调用,从Pin中取出内部值。
12. 总结
Section titled “12. 总结”std::pin 模块提供了 Rust 内存固定机制:
| 组件 | 作用 |
|---|---|
Pin | 将值固定到内存位置 |
Unpin | 标记可以被移动的类型(auto trait) |
PhantomPinned | 将类型标记为不可移动 |
pin_mut! | 便捷宏,固定局部变量 |
Pin 是 Rust 异步编程的基础,正确理解它对于编写高效的异步代码至关重要。