Expand description
键入将数据固定到其在内存中的位置的类型。
从对象在内存中的位置不变的意义上讲,保证对象不移动有时很有用。 这种情况的一个主要例子是构建自引用结构,因为移动一个带有指向自身的指针的对象会使它们无效,这可能导致未定义的行为。
在高层次上,Pin<P>
确保任何指针类型 P
的指针在内存中都有一个稳定的位置,这意味着它不能被移动到其他地方,并且它的内存不能被释放,直到它被丢弃。我们说该对象是 “pinned”。当讨论将固定数据与非固定数据结合在一起的类型时,事情变得更加微妙。查看下文 了解更多详细信息。
默认情况下,Rust 中的所有类型都是可移动的。
Rust 允许按值传递所有类型,以及常见的智能指针类型,例如 Box<T>
和 &mut T
允许替换和移动它们包含的值:您可以移出 Box<T>
,或者您可以使用 mem::swap
。Pin<P>
包装一个指针类型 P
,所以 Pin<Box<T>>
函数很像一个普通的 Box<T>
:
当 Pin<Box<T>>
被丢弃,其内容也被丢弃,内存被释放。类似地,Pin<&mut T>
很像 &mut T
。
然而,Pin<P>
不让客户实际获得 Box<T>
或 &mut T
固定数据,这意味着您不能使用 mem::swap
之类的操作:
use std::pin::Pin;
fn swap_pins<T>(x: Pin<&mut T>, y: Pin<&mut T>) {
// `mem::swap` 需要 `&mut T`,但我们无法得到它。
// 我们被困住了,我们不能交换这些引用的内容。
// 我们可以使用 `Pin::get_unchecked_mut`,但这是不安全的,原因如下:
// 我们不允许将其用于将物品移出 `Pin`。
}
Run值得重申的是 Pin<P>
不会改变一个事实,即 Rust 编译器认为所有类型都是可移动的。
mem::swap
仍可用于任何 T
。
相反,Pin<P>
防止某些值 (由 Pin<P>
) 使其无法调用需要 &mut T
方法 (如 mem::swap
) 而被移动。
Pin<P>
可用于包装任何指针类型 P
,因此它与 Deref
和 DerefMut
交互。Pin<P>
其中 P: Deref
应被视为固定 P::Target
的 “P
-style pointer” - 因此,Pin<Box<T>>
是指向固定 T
的拥有指针,以及 Pin<Rc<T>>
Pin<Rc>
是指向固定 T
的引用计数指针。
为正确起见,Pin<P>
依赖于 Deref
和 DerefMut
的实现不会移出它们的 self
参数,并且只在固定指针上调用它们时才返回指向固定数据的指针。
Unpin
即使不固定,许多类型也始终可以自由移动,因为它们不依赖于具有稳定的地址。这包括所有原始类型 (如 bool
,i32
和引用) 以及仅由这些类型组成的类型。不关心 pinning 的类型实现了 Unpin
auto-trait,取消了 Pin<P>
.
对于 T: Unpin
, Pin<Box<T>>
和 Box<T>
函数相同,Pin<&mut T>
和 &mut T
。
请注意,固定和 Unpin
仅影响指向类型 P::Target
,而不影响包含在 Pin<P>
的指针类型 P
本身.
例如,是否 Box<T>
是 Unpin
对 Pin<Box<T>>
的行为没有影响(这里,T
是指向类型)。
示例:自引用结构体
在我们详细解释与 Pin</code> 相关的保证和选择之前
Pin<P>
,我们讨论了一些如何使用它的例子。
请随意 跳到理论讨论的地方继续。
use std::pin::Pin;
use std::marker::PhantomPinned;
use std::ptr::NonNull;
// 这是一个自引用结构体,因为切片字段指向数据字段。
// 我们无法通过正常的引用将其告知编译器,因为无法使用通常的借用规则来描述此模式。
//
// 取而代之的是,我们使用一个裸指针,尽管我们知道它指向的是一个不为 null 的指针。
//
struct Unmovable {
data: String,
slice: NonNull<String>,
_pin: PhantomPinned,
}
impl Unmovable {
// 为了确保函数返回时数据不会移动,我们将其放置在堆中,以保留对象的生命周期,唯一的访问方法是通过指向它的指针。
//
//
fn new(data: String) -> Pin<Box<Self>> {
let res = Unmovable {
data,
// 我们仅在数据到位后创建指针,否则数据将在我们开始之前就已经移动
//
slice: NonNull::dangling(),
_pin: PhantomPinned,
};
let mut boxed = Box::pin(res);
let slice = NonNull::from(&boxed.data);
// 我们知道这是安全的,因为修改字段不会移动整个结构体
unsafe {
let mut_ref: Pin<&mut Self> = Pin::as_mut(&mut boxed);
Pin::get_unchecked_mut(mut_ref).slice = slice;
}
boxed
}
}
let unmoved = Unmovable::new("hello".to_string());
// 只要结构体没有移动,指针应指向正确的位置。
//
// 同时,我们可以随意移动指针。
let mut still_unmoved = unmoved;
assert_eq!(still_unmoved.slice, NonNull::from(&still_unmoved.data));
// 由于我们的类型未实现 Unpin,因此无法编译:
// let mut new_unmoved = Unmovable::new("world".to_string());
// std::mem::swap(&mut *still_unmoved, &mut *new_unmoved);
Run示例:侵入式双向链表列表
在侵入式双向链表中,集合实际上并未为元素本身分配内存。 分配由客户端控制,元素可以驻留在比集合短的栈框架上。
为了使此工作有效,列表中的每个元素都有指向其前任和后任的指针。元素只能在固定时添加,因为四处移动元素会使指针无效。此外,链表元素的 Drop
实现将修补其前任和后继的指针以将其从列表中删除。
至关重要的是,我们必须能够依靠被调用的 drop
。如果在不调用 drop
的情况下可以释放元素或使元素无效,则来自其相邻元素的指针将变为无效,这将破坏数据结构体。
因此,固定还会附带 丢弃 相关的保证。
Drop
保证
固定的目的是能够依靠某些数据在内存中的放置。
为了使这项工作有效,不仅限制了移动数据,还限制了数据的传输。限制用于存储数据的内存的重新分配,重新分配用途或以其他方式使之无效。
具体来说,对于固定的数据,必须保持不变,即从固定 its memory 到调用 drop
,*its memory 都不会失效或重新使用。只有 drop
返回或 panics,才可以重用该内存。
内存可以通过释放为 “invalidated”,也可以通过将 Some(v)
替换为 None
,或将 vector 中的某些元素从 Vec::set_len
调用到 “kill”。可以通过使用 ptr::write
覆盖它来重新利用它,而无需先调用析构函数。在不调用 drop
的情况下,不允许对固定数据进行任何此操作。
这正是上一节中的侵入式链表需要正确执行函数的一种保证。
请注意,此保证不代表内存不会泄漏! 永远不要在固定元素上调用 drop
仍然是完全可以的 (例如,您仍然可以在 Pin<Box<T>>
)。在双向链表的示例中,该元素将仅保留在列表中。但是,您不得释放或重用调用 drop
* 的存储 *without。
Drop
实现
如果您的类型使用固定 (例如上面的两个示例),则在实现 Drop
时必须小心。drop
函数采用 &mut self
,但这被称为即使您的类型之前已固定! 好像编译器自动调用了 Pin::get_unchecked_mut
。
这绝不会导致安全代码出现问题,因为实现依赖于固定的类型需要不安全的代码,但请注意,决定在您的类型中使用固定 (例如通过在 Pin<&Self>
或 Pin<&mut Self>
上实现某些操作) 会对您的 Drop
实现也是如此: 如果您的类型的元素可以被固定,您必须将 Drop
视为隐式采用 Pin<&mut Self>
。
例如,您可以按如下方式实现 Drop
:
impl Drop for Type {
fn drop(&mut self) {
// `new_unchecked` 可以,因为我们知道这个值在被丢弃后再也不会使用了。
//
inner_drop(unsafe { Pin::new_unchecked(self)});
fn inner_drop(this: Pin<&mut Type>) {
// 实际丢弃的代码在此处。
}
}
}
Run函数 inner_drop
具有 应该 具有 drop
的类型,因此可以确保您不会以与固定冲突的方式意外使用 self
/this
。
此外,如果您的类型是 #[repr(packed)]
,则编译器将自动移动字段以将其删除。它甚至可以对恰好足够对齐的字段执行此操作。因此,您不能使用 #[repr(packed)]
类型的固定。
投影和结构固定
在使用固定结构体时,问题是如何在只需要 Pin<&mut 结构体>
的方法中访问该结构体的字段。
通常的方法是编写辅助方法 (所谓的 projections),将 Pin<&mut 结构体>
转换为对字段的引用,但该引用应该具有什么类型? 是 Pin<&mut Field>
还是 &mut Field
?
enum
的字段以及在考虑 container/wrapper 类型 (例如 Vec<T>
, Box<T>
,或 RefCell<T>
.
(此问题适用于可变引用和共享引用,我们仅在此处使用可变引用的更常见情况进行说明。)
事实证明,实际上是由数据结构的作者决定特定字段的固定 projection 是将 Pin<&mut 结构体 >
转换为 Pin<&mut Field>
或 &mut Field
.但是有一些约束,最重要的约束是 consistency:
每个字段都可以 或者 投影到固定的引用,或者 * 可以删除固定作为投影的一部分。
如果两者都是针对同一个字段进行的,那很可能是不合理的!
作为数据结构体的作者,您可以为每个字段决定是否将 “propagates” 固定到该字段。 传播的固定也称为 “structural”,因为它遵循该类型的结构体。 在以下各小节中,我们描述了两种选择都必须考虑的因素。
Pinning 不是用于结构体的 field
固定结构体的字段可能不被固定似乎违反直觉,但这实际上是最简单的选择:如果从未创建 Pin<&mut Field>
则不会出错! 因此,如果您确定某个字段不具有结构固定,则只需确保您从未创建对该字段的固定引用即可。
没有结构固定的字段可能具有将 Pin<&mut 结构体 >
转换为 &mut Field
的 projection 方法:
impl Struct {
fn pin_get_field(self: Pin<&mut Self>) -> &mut Field {
// 可以,因为 `field` 从未被视为固定。
unsafe { &mut self.get_unchecked_mut().field }
}
}
Run您也可以 impl Unpin for Struct
即使 field
的类型不是 Unpin
. 当没有创建 Pin<&mut Field>
时,该类型对固定的看法 Pin<&mut Field>
。
Pinning 是结构体的 field
另一个选择是确定钉扎是 field
还是 field
,这意味着如果钉扎结构体,则字段也钉扎。
这允许编写一个创建 Pin<&mut Field>
的 projection,从而见证该字段被固定:
impl Struct {
fn pin_get_field(self: Pin<&mut Self>) -> Pin<&mut Field> {
// 可以,因为 `self` 固定在 `field` 上。
unsafe { self.map_unchecked_mut(|s| &mut s.field) }
}
}
Run但是,结构固定需要一些额外的要求:
-
如果所有结构字段均为
Unpin
,则结构体必须仅为Unpin
。这是默认值,但Unpin
是一个安全的 trait,因此作为结构体的作者,您有责任不添加类似impl[Unpin] for 结构体 <T>
. (请注意,添加投影操作需要不安全的代码,因此Unpin
是安全的 trait 的事实并没有破坏您只需要在使用unsafe
时担心任何这些的原则。) -
结构体的析构函数不得将结构域移出其参数。这正是 上一节 中提出的要点:
drop
采用&mut self
,但是结构体 (以及它的字段) 之前可能已经被固定了. 您必须保证不会在您的Drop
实现中移动任何字段。特别是,如前所述,这意味着您的结构体 不能 为#[repr(packed)]
。 有关如何编写drop
的方法,请参见该部分,以使编译器可以帮助您避免意外破坏固定。 -
您必须确保遵守使用
Drop
保证: 一旦固定了您的结构体,包含内容的内存就不会被覆盖或释放,而无需调用内容的析构函数。 这可能很棘手,正如VecDeque<T>
的析构函数VecDeque<T>
, 如果析构函数 panics 之一,则可能无法在所有元素上调用drop
。这违反了Drop
保证,因为它可能导致元素在没有调用析构函数的情况下被释放。 (VecDeque<T>
没有固定 projection,所以这不会导致不稳定。) -
固定类型时,不得提供可能导致数据移出结构字段的任何其他操作。例如,如果结构体包含一个
Option<T>
并且有一个类似take
的操作,类型为fn (Pin<&mut 结构体 >) -> Option<T>
fn (Pin<&mut 结构体 >) -> Option<T>
,该操作可用于将T
从固定的Struct<T>
中移出 - 这意味着固定不能对保存此数据的字段进行结构化。有关将数据移出固定类型的更复杂示例,请想象如果
RefCell<T>
有一个方法fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T>
。 然后,我们可以执行以下操作:ⓘ
Runfn exploit_ref_cell<T>(rc: Pin<&mut RefCell<T>>) { { let p = rc.as_mut().get_pin_mut(); } // 在这里,我们可以固定访问 `T`。 let rc_shr: &RefCell<T> = rc.into_ref().get_ref(); let b = rc_shr.borrow_mut(); let content = &mut *b; // 这里我们有 `&mut T` 到相同的数据。 }
这是灾难性的,这意味着我们可以先固定
RefCell<T>
(使用RefCell::get_pin_mut
) 然后使用我们稍后获得的RefCell::get_pin_mut
引用移动该内容。
Examples
对于像 Vec<T>
这样的类型,两种可能性 (结构固定与否) 都有意义。Vec<T>
使用结构固定可以有 get_pin
/get_pin_mut
方法来固定引用到元素。但是,它可能不允许在固定的 Vec<T>
上调用 pop
因为那会移动 (结构固定的) 内容! 它也不允许 push
,它可能会重新分配并因此也移动内容。
Vec<T>
没有结构固定可以 impl[Unpin] for Vec<T>
impl[Unpin] for Vec<T>
,因为内容永远不会被固定并且 Vec<T>
本身也可以移动。
那时,固定对 vector 完全没有影响。
在标准库中,指针类型通常不具有结构固定,因此它们不提供固定投影。这就是为什么 Box<T>: Unpin
适用于所有 T
。对指针类型这样做是有意义的,因为移动 Box<T>
实际上并没有移动 T
: Box<T>
即使 T
不是,也可以自由移动 (又名 Unpin
)。
事实上,即使 Pin<Box<T>>
和 Pin<&mut T>
总是 Unpin
本身,原因相同:
它们的内容 (T
) 是固定的,但指针本身可以在不移动固定数据的情况下移动。
对于 Box<T>
和 Pin<Box<T>>
,内容是否固定完全独立于指针是否固定,意味着固定是非结构的。
当实现 Future
组合器时,通常需要对嵌套的 futures 进行结构钉扎,因为您需要将引用的钉扎到 poll
上。
但是,如果您的组合器包含任何其他不需要固定的数据,您可以使这些字段不是结构化的,因此即使您只有 Pin<&mut Self>
(例如在您自己的 poll
实现中)。
Macros
Structs
- 固定的指针。