Rust 中的 `std::pin::Pin` 是什么?

Lobsters Hottest 新闻

摘要

解释 Rust 中的 `std::pin::Pin` 类型,为什么自引用类型(如异步状态机)需要固定(pinning),以及 `Pin` 如何防止移动同时允许安全修改。

<p><a href="https://lobste.rs/s/ltzfkv/what_is_std_pin_pin_rust">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/06/29 18:30

# Rust 中的 `std::pin::Pin` 是什么? 来源:https://vrong.me/blog/what-is-pinning-in-rust/ `std::pin::Pin` 是一个指针包装器,它表示一个保证:通过该指针指向的对象不会被移动。 ## 为什么需要它? 需要固定(pinning)的根本原因来自自引用类型(self-referential types)。自引用结构体是地址敏感类型(address-sensitive types)的最常见例子,也是 `Pin` 的主要动机。以下面的结构体为例: ```rust 1struct SelfRef { 2 data: i32, 3 ptr: *const i32, // 指向 self.data 4} ``` 如果我们移动这个结构体的实例(例如,通过将所有权转移到另一个变量或返回它),它的内存地址会改变。然而,裸指针 `ptr` 仍然指向旧的内存位置,从而产生悬垂指针。因此,我们需要一种方法,在建立这些自引用之后,防止移动 `SelfRef`。 ### 我们在哪里遇到这个问题? 我们在使用 `async`/`await` 和 `Futures` 时最常遇到这个问题。跨 `.await` 点生存的局部变量会成为编译器生成的状态机中的字段。如果对一个局部变量的引用也跨过同一个 `.await` 点生存,那么生成的 future 就会变成自引用的。一旦开始轮询(polling),future 可能依赖指向自身其他字段的内部引用。之后移动 future 会使这些引用失效。为了防止这一点,`Future::poll` 方法要求 future 被固定: ```rust 1pub trait Future { 2 type Output; 3 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; 4} ``` 通过接受 `Pin<&mut Self>` 而不是 `&mut self`,`poll` 的调用者被要求保证 future 在开始轮询后不会被移动。 ## 固定是如何工作的? `Pin` 阻止安全代码通过该指针移动目标,同时仍然允许对固定值进行普通的修改。 ### `&mut T` 的问题 如果你有一个可变引用 `&mut T`,像 `mem::replace`、`mem::swap` 或赋值这样的函数可以重新定位存储在该内存位置的值。`Pin` 限制了你恢复普通可变引用的能力。安全代码无法从 `Pin<&mut T>` 恢复普通的 `&mut T`,除非 `T: Unpin`。 ```rust 1impl<'a, T: ?Sized> Pin<&'a T> { 2 pub const fn get_ref(self) -> &'a T { ... } 3} 4 5impl<'a, T: ?Sized> Pin<&'a mut T> { 6 pub const fn get_mut(self) -> &'a mut T 7 where 8 T: Unpin 9 { ... } 10} ``` 重要:`Pin` 只阻止通过固定指针的移动;它不阻止对固定值的修改。固定类型的方法可以自由地修改其字段,只要它们不移动字段即可。如果类型没有实现 `Unpin`(即它是 `!Unpin`),你就无法通过安全代码获得 `&mut T`。你必须使用像 `Pin::get_unchecked_mut` 这样的 `unsafe` 方法,向编译器表明你承诺不会通过该引用将值移出。 ### 什么是 `Unpin`? 实现了 `Unpin` 的类型不依赖固定来保证正确性。 ```rust // std::marker pub auto trait Unpin {} ``` Rust 中的大多数类型(如 `i32`、`String`、`Vec` 等)不关心是否被移动,因此默认是 `Unpin`。除非显式实现了 `!Unpin`,否则所有类型自动实现 `Unpin`。 提示:标记结构体 `std::marker::PhantomPinned` 显式地是 `!Unpin`。由于自动 trait 会自动传播,任何包含 `PhantomPinned` 字段的结构体也会自动成为 `!Unpin`。 ```rust 1use std::marker::PhantomPinned; 2 3struct SelfRef { 4 data: i32, 5 ptr: *const i32, 6 _phantom: PhantomPinned, // 使整个结构体成为 !Unpin 7} ``` 这是声明自定义结构体在固定后移动不安全的标准方式。由于编译器无法自动检测自引用(通常通过不安全的裸指针创建),它无法自动将这样的结构体标记为 `!Unpin`。因此,这依赖于一个约定:开发者必须为任何自引用结构体显式选择退出 `Unpin`(通常通过嵌入一个 `PhantomPinned` 字段)。如果自引用类型错误地保持 `Unpin`,安全代码可以从 `Pin` 中恢复可变引用并移动该值,从而违反创建自引用的不安全代码所做的假设。 `Pin` 不会物理上阻止值的移动。相反,它是一种类型层面的保证,即该值不会通过该指针被移动。因此,安全地构造一个 `Pin` 需要确保目标在该 pin 的生命周期内保持在一个稳定的内存位置上。 ## 如何构造一个 `Pin` `Pin` 本身并不会固定一个值。相反,构造一个 `Pin` 意味着证明目标在该 pin 的生命周期内将保持在一个稳定的内存位置上。 ### `Pin::new` 构造 Pin 的最简单方法是使用 `Pin::new`: ```rust 1let mut value = 42; 2let pinned = Pin::new(&mut value); ``` 然而,这个构造函数只有在 `T: Unpin` 时才可用。由于 `Unpin` 类型不依赖固定来保证正确性,将它们包装在 `Pin` 中总是安全的。固定保证实际上是一个空操作。 ### `pin!` 当你需要在不进行堆分配的情况下在本地固定一个值时,可以使用 `pin!` 宏: ```rust 1use std::pin::pin; 2 3let future = pin!(async { 4 println!("Hello"); 5}); ``` 该宏创建一个局部变量,并返回一个指向它的 `Pin<&mut T>`。编译器保证该局部变量在其生命周期剩余部分不会被移动,这使得它成为在栈上安全固定 `!Unpin` 值的方法。 警告:尽管名字叫 `pin!`,但它并没有固定栈内存本身。它创建了一个固定引用,其生命周期与局部变量绑定。一旦变量离开作用域,固定保证就结束了。 ### `Box::pin` 对于 `!Unpin` 类型,最常用的构造函数是 `Box::pin`: ```rust 1let pinned = Box::pin(SelfRef { ... }); ``` 与创建绑定到局部变量的 `Pin<&mut T>` 的 `pin!` 不同,`Box::pin` 返回一个 `Pin<Box<T>>`,其生命周期由 `Box` 拥有。由于堆分配本身不会移动,目标在 `Box` 的生命周期内拥有一个稳定的内存位置,因此可以安全地固定。 注意:移动 `Box` 本身并不会移动它拥有的值。只有存储在 `Box` 内部的指针会被移动;堆分配仍然位于同一个地址。 ### `Pin::new_unchecked` 有时安全构造函数无法证明一个值将保持在原位。在这些情况下,不安全的代码可以手动构造一个 `Pin`: ```rust 1let pinned = unsafe { Pin::new_unchecked(ptr) }; ``` 通过调用 `Pin::new_unchecked`,调用者承诺目标不会再通过任何指针被移动,且这个承诺在生成的 `Pin` 的生命周期内有效。如果这个承诺被违反,任何依赖固定保证的代码都可能表现出未定义行为。因此,`Pin::new_unchecked` 通常只在实现能够维持这个不变量的底层抽象时使用。 ## 你实际上什么时候需要关心? 对于大多数 Rust 开发者来说,`Pin` 和 `Unpin` 在后台安静地工作。你通常只需要在两种场景下考虑它们: 1. **使用异步代码:** 如果你需要手动轮询一个 future,或者将一个 future 传递给需要固定 future 的 API,可以使用 `Box::pin(future)`(在堆上固定它)或 `std::pin::pin!(future)`(在栈上本地固定它)。 2. **手动实现 `Future`:** 如果你正在编写一个自定义状态机或其他底层异步原语,你将需要处理 `Pin<&mut Self>`,并可能需要使用 `PhantomPinned` 和不安全代码来维护固定不变式。 最终,`Pin` 是 Rust 针对地址敏感类型问题的零成本解决方案。它使得方便的 `async`/`await` 和其他自引用抽象成为可能,同时保留了 Rust 的内存安全保证,且不需要垃圾回收器。

相似文章

没人要求的 `Sync` 约束

Lobsters Hottest

解释了在具有 `Send` future 的异步 trait 方法中使用 `&self` 如何隐式要求实现类型具有 `Sync`,并提供了诸如改用 `&mut self` 或使用 `Sync` 内部可变性等解决方法。

谁在运行你的 Rust Future?动手实践入门异步 Rust

Hacker News Top

这是一套动手实践教程系列,旨在弥合理解异步 Rust 内部机制(Future、poll、Pin、执行器)与使用 Tokio 部署实际异步代码之间的差距,面向熟悉 JavaScript 异步和基础 Rust 的开发者。

iddqd:最难的一种不安全Rust

Lobsters Hottest

本文介绍了 iddqd,这是一个 Rust 库,它提供了从值中借用键的映射,减少了重复和同步问题。本文讨论了编写不安全 Rust 代码的挑战以及该库如何保持正确性。