Rust Unpin

即使不固定,许多类型也始终可以自由移动,因为它们不依赖于具有稳定的地址。这包括所有原始类型 (如 bool,i32 和引用) 以及仅由这些类型组成的类型。不关心 pinning 的类型实现了 Unpin auto-trait,取消了 Pin

. 对于 T: Unpin, Pin<Box>和 Box 函数相同,Pin<&mut T> 和 &mut T。

请注意,固定和 Unpin 仅影响指向类型 P::Target,而不影响包含在 Pin

的指针类型 P 本身. 例如,是否 Box 是 Unpin 对 Pin<Box> 的行为没有影响(这里,T 是指向类型)。

示例:自引用结构体

在我们详细解释与 Pin 相关的保证和选择之前 Pin

,我们讨论了一些如何使用它的例子。
请随意 跳到理论讨论的地方继续。

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);

示例:侵入式双向链表列表

在侵入式双向链表中,集合实际上并未为元素本身分配内存。 分配由客户端控制,元素可以驻留在比集合短的栈框架上。

为了使此工作有效,列表中的每个元素都有指向其前任和后任的指针。元素只能在固定时添加,因为四处移动元素会使指针无效。此外,链表元素的 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>)。在双向链表的示例中,该元素将仅保留在列表中。但是,您不得释放或重用调用 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>) {
            // 实际丢弃的代码在此处。
        }
    }
}

函数 inner_drop 具有 应该 具有 drop 的类型,因此可以确保您不会以与固定冲突的方式意外使用 self/this。

此外,如果您的类型是 #[repr(packed)],则编译器将自动移动字段以将其删除。它甚至可以对恰好足够对齐的字段执行此操作。因此,您不能使用 #[repr(packed)] 类型的固定。

投影和结构固定

在使用固定结构体时,问题是如何在只需要 Pin<&mut 结构体> 的方法中访问该结构体的字段。 通常的方法是编写辅助方法 (所谓的 projections),将 Pin<&mut 结构体> 转换为对字段的引用,但该引用应该具有什么类型? 是 Pin<&mut Field> 还是 &mut Field? enum 的字段以及在考虑 container/wrapper 类型 (例如 Vec, Box,或 RefCell. (此问题适用于可变引用和共享引用,我们仅在此处使用可变引用的更常见情况进行说明。)

事实证明,实际上是由数据结构的作者决定特定字段的固定 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 }
    }
}

您也可以 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) }
    }
}

但是,结构固定需要一些额外的要求:

如果所有结构字段均为 Unpin,则结构体必须仅为 Unpin。这是默认值,但 Unpin 是一个安全的 trait,因此作为结构体的作者,您有责任不添加类似 impl[Unpin] for 结构体 . (请注意,添加投影操作需要不安全的代码,因此 Unpin 是安全的 trait 的事实并没有破坏您只需要在使用 unsafe 时担心任何这些的原则。)

结构体的析构函数不得将结构域移出其参数。这正是 上一节 中提出的要点: drop 采用 &mut self,但是结构体 (以及它的字段) 之前可能已经被固定了. 您必须保证不会在您的 Drop 实现中移动任何字段。特别是,如前所述,这意味着您的结构体 不能 为 #[repr(packed)]。 有关如何编写 drop 的方法,请参见该部分,以使编译器可以帮助您避免意外破坏固定。

您必须确保遵守使用 Drop 保证: 一旦固定了您的结构体,包含内容的内存就不会被覆盖或释放,而无需调用内容的析构函数。 这可能很棘手,正如 VecDeque 的析构函数 VecDeque, 如果析构函数 panics 之一,则可能无法在所有元素上调用 drop。这违反了 Drop 保证,因为它可能导致元素在没有调用析构函数的情况下被释放。 (VecDeque 没有固定 projection,所以这不会导致不稳定。)

固定类型时,不得提供可能导致数据移出结构字段的任何其他操作。例如,如果结构体包含一个 Option并且有一个类似 take 的操作,类型为 fn (Pin<&mut 结构体 >) -> Option fn (Pin<&mut 结构体 >) -> Option,该操作可用于将 T 从固定的 Struct 中移出 - 这意味着固定不能对保存此数据的字段进行结构化。

有关将数据移出固定类型的更复杂示例,请想象如果 RefCell有一个方法 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` 到相同的数据。
}

这是灾难性的,这意味着我们可以先固定 RefCell (使用 RefCell::get_pin_mut ) 然后使用我们稍后获得的 RefCell::get_pin_mut 引用移动该内容。

Examples

对于像 Vec 这样的类型,两种可能性 (结构固定与否) 都有意义。Vec 使用结构固定可以有 get_pin/get_pin_mut 方法来固定引用到元素。但是,它可能不允许在固定的 Vec 上调用 pop因为那会移动 (结构固定的) 内容! 它也不允许 push,它可能会重新分配并因此也移动内容。

Vec没有结构固定可以 impl[Unpin] for Vec impl[Unpin] for Vec,因为内容永远不会被固定并且 Vec 本身也可以移动。 那时,固定对 vector 完全没有影响。

在标准库中,指针类型通常不具有结构固定,因此它们不提供固定投影。这就是为什么 Box: Unpin 适用于所有 T。对指针类型这样做是有意义的,因为移动 Box 实际上并没有移动 T: Box 即使 T 不是,也可以自由移动 (又名 Unpin)。 事实上,即使 Pin<Box>和 Pin<&mut T> 总是 Unpin 本身,原因相同: 它们的内容 (T) 是固定的,但指针本身可以在不移动固定数据的情况下移动。 对于 Box 和 Pin<Box>,内容是否固定完全独立于指针是否固定,意味着固定是非结构的。

当实现 Future 组合器时,通常需要对嵌套的 futures 进行结构钉扎,因为您需要将引用的钉扎到 poll 上。 但是,如果您的组合器包含任何其他不需要固定的数据,您可以使这些字段不是结构化的,因此即使您只有 Pin<&mut Self> (例如在您自己的 poll 实现中)。

Macros

pin 通过在本地固定 value: T 来构建 Pin<&mut T>。

Structs

Pin 固定的指针。

推荐几款学习编程的免费平台

免费在线开发平台(https://docs.ltpp.vip/LTPP/

       探索编程世界的新天地,为学生和开发者精心打造的编程平台,现已盛大开启!这个平台汇集了近4000道精心设计的编程题目,覆盖了C、C++、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3以及C#等众多编程语言,为您的编程学习之旅提供了一个全面而丰富的实践环境。       
      在这里,您不仅可以查看自己的代码记录,还能轻松地在云端保存和运行代码,让编程变得更加便捷。平台还提供了私聊和群聊功能,让您可以与同行们无障碍交流,分享文件,共同进步。不仅如此,您还可以通过阅读文章、参与问答板块和在线商店,进一步拓展您的知识边界。
       为了提升您的编程技能,平台还设有每日一题、精选题单以及激动人心的编程竞赛,这些都是备考编程考试的绝佳资源。更令人兴奋的是,您还可以自定义系统UI,选择视频或图片作为背景,打造一个完全个性化的编码环境,让您的编程之旅既有趣又充满挑战。

免费公益服务器(https://docs.ltpp.vip/LTPP-SHARE/linux.html

       作为开发者或学生,您是否经常因为搭建和维护编程环境而感到头疼?现在,您不必再为此烦恼,因为一款全新的免费公共服务器已经为您解决了所有问题。这款服务器内置了多种编程语言的编程环境,并且配备了功能强大的在线版VS Code,让您可以随时随地在线编写代码,无需进行任何复杂的配置。
随时随地,云端编码
       无论您身在何处,只要有网络连接,就可以通过浏览器访问这款公共服务器,开始您的编程之旅。这种云端编码的便利性,让您的学习或开发工作不再受限于特定的设备或环境。
丰富的编程语言支持
       服务器支持包括C、C++、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3以及C#等在内的多种主流编程语言,满足不同开发者和学生的需求。无论您是初学者还是资深开发者,都能找到适合自己的编程环境。
在线版VS Code,高效开发
       内置的在线版VS Code提供了与本地VS Code相似的编辑体验,包括代码高亮、智能提示、代码调试等功能,让您即使在云端也能享受到高效的开发体验。
数据隐私和安全提醒
       虽然服务器是免费的,但为了保护您的数据隐私和安全,我们建议您不要上传任何敏感或重要的数据。这款服务器更适合用于学习和实验,而非存储重要信息。

免费公益MYSQL(https://docs.ltpp.vip/LTPP-SHARE/mysql.html

       作为一名开发者或学生,数据库环境的搭建和维护往往是一个复杂且耗时的过程。但不用担心,现在有一款免费的MySQL服务器,专为解决您的烦恼而设计,让数据库的使用变得简单而高效。
性能卓越,满足需求
       虽然它是免费的,但性能绝不打折。服务器提供了稳定且高效的数据库服务,能够满足大多数开发和学习场景的需求。
在线phpMyAdmin,管理更便捷
       内置的在线phpMyAdmin管理面板,提供了一个直观且功能强大的用户界面,让您可以轻松地查看、编辑和管理数据库。
数据隐私提醒,安全第一
       正如您所知,这是一项公共资源,因此我们强烈建议不要上传任何敏感或重要的数据。请将此服务器仅用于学习和实验目的,以确保您的数据安全。

免费在线WEB代码编辑器(https://docs.ltpp.vip/LTPP-WEB-IDE/

       无论你是开发者还是学生,编程环境的搭建和管理可能会占用你宝贵的时间和精力。现在,有一款强大的免费在线代码编辑器,支持多种编程语言,让您可以随时随地编写和运行代码,提升编程效率,专注于创意和开发。
多语言支持,无缝切换
       这款在线代码编辑器支持包括C、C++、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3以及C#在内的多种编程语言,无论您的项目需要哪种语言,都能在这里找到支持。
在线运行,快速定位问题
       您可以在编写代码的同时,即时运行并查看结果,快速定位并解决问题,提高开发效率。
代码高亮与智能提示
       编辑器提供代码高亮和智能提示功能,帮助您更快地编写代码,减少错误,提升编码质量。

免费二维码生成器(https://docs.ltpp.vip/LTPP-QRCODE/

       二维码(QR Code)是一种二维条码,能够存储更多信息,并且可以通过智能手机等设备快速扫描识别。它广泛应用于各种场景,如:
企业宣传
       企业可以通过二维码分享公司网站、产品信息、服务介绍等。
活动推广
       活动组织者可以创建二维码,参与者扫描后可以直接访问活动详情、报名链接或获取电子门票。
个人信息分享
       个人可以生成包含联系方式、社交媒体链接、个人简历等信息的二维码。
电子商务
       商家使用二维码进行商品追踪、促销活动、在线支付等。
教育
       教师可以创建二维码,学生扫描后可以直接访问学习资料或在线课程。
交通出行
       二维码用于公共交通的票务系统,乘客扫描二维码即可进出站或支付车费。        功能强大的二维码生成器通常具备用户界面友好,操作简单,即使是初学者也能快速上手和生成的二维码可以在各种设备和操作系统上扫描识别的特点。

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WA-自动机

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值