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