Rust 中的 Pin 和 Unpin:内存安全与异步编程的守护者

在 Rust 的世界里,PinUnpin 是两个看似不起眼、实则至关重要的概念。它们在内存安全和异步编程中扮演着关键角色,是 Rust 开发者必须掌握的知识。今天,就让我们深入探讨这两个概念,看看它们是如何在 Rust 的生态系统中发挥作用的。

一、Pin:固定值的“魔法”

想象一下,你正在处理一个复杂的程序,其中包含了许多动态分配的内存和指针操作。在这种情况下,确保内存中的值不会被意外移动是非常重要的,因为这可能会导致指针失效,进而引发各种难以调试的错误。而 Pin,就像是一个神奇的“钉子”,能够将值固定在内存中的某个位置,防止它们被移动。

1. Pin 的作用

Pin<P> 是一个智能指针,它包装了任意的指针类型 P。它的主要功能是确保包装的值在内存中的位置保持不变。这听起来可能有点抽象,但其实它的应用场景非常广泛,尤其是在处理自引用类型时。

自引用类型是指一个类型内部包含指向其自身字段的指针。例如,考虑以下结构体:

struct SelfRef {value: String,pointer_to_value: *mut String,
}

在这个结构体中,pointer_to_value 是一个裸指针,指向 value 字段。如果 value 被移动,pointer_to_value 将会指向一个无效的地址,从而导致内存安全问题。而 Pin 可以防止这种情况发生。通过将 SelfRef 固定在内存中的某个位置,Pin 确保 value 的地址不会改变,从而保证 pointer_to_value 始终有效。

2. Pin 的使用

Pin 的使用非常简单。你可以通过 Pin::newPin::new_unchecked 来创建一个 Pin。例如:

use std::pin::Pin;fn main() {let mut value = String::from("Hello, world!");let pinned_value = Pin::new(&mut value);
}

在这个例子中,pinned_value 是一个 Pin<&mut String>,它将 value 固定在内存中的某个位置。需要注意的是,Pin::new 只能用于那些已经实现了 Unpin 的类型。如果类型没有实现 Unpin,你需要使用 Pin::new_unchecked,但这需要你非常小心,因为如果使用不当,可能会导致内存安全问题。

二、Unpin:自由移动的“通行证”

Pin 相对的是 UnpinUnpin 是一个标记 trait,表示对象可以安全地被移动。默认情况下,Rust 为大多数类型自动实现了 Unpin,这意味着这些类型的值可以在内存中自由移动。

1. Unpin 的作用

Unpin 的存在主要是为了与 Pin 配合使用。如果一个类型实现了 Unpin,那么它就可以被自由地移动,即使它被包装在 Pin 中。这听起来可能有点矛盾,但其实这是非常合理的。因为如果一个类型不需要固定在内存中的某个位置,那么就没有必要限制它的移动。

例如,以下代码展示了 Unpin 的自动实现:

struct MyStruct {data: String,
}fn move_struct<T>(val: T) -> T {val
}fn main() {let mut s = MyStruct { data: String::from("Rust") };let pinned_s = Pin::new(&mut s);let s = move_struct(s); // 因为实现了 `Unpin`,可以自由移动println!("{}", s.data);
}

在这个例子中,MyStruct 自动实现了 Unpin,因此即使我们使用 Pin 将其固定,它仍然可以被移动。

2. 阻止类型被移动

如果你希望某个类型在被 Pin 固定后不能被移动,可以通过引入 PhantomPinned 来阻止 Rust 自动为该类型实现 Unpin。例如:

use std::pin::PhantomPinned;struct NoMove {data: String,_pin: PhantomPinned,
}fn move_struct<T>(val: T) -> T {val
}fn main() {let mut s = NoMove { data: String::from("No Move"), _pin: PhantomPinned };let pinned_s = Pin::new(&mut s);// let s = move_struct(s); // 编译错误,因为 `NoMove` 没有实现 `Unpin`println!("{}", pinned_s.data);
}

在这个例子中,NoMove 类型没有实现 Unpin,因此它在被固定后不能被移动。

三、异步编程中的 Pin 和 Unpin

在 Rust 的异步编程模型中,PinUnpin 尤其重要。Future trait 的 poll 方法要求 selfPin<&mut Self>,确保在轮询期间对象不会被移动。

1. 异步编程中的 Pin

在异步编程中,Pin 的作用是确保 Future 在被轮询时不会被移动。这是因为 Future 可能会包含一些需要固定在内存中的状态。例如:

use std::pin::Pin;
use std::future::Future;
use std::task::{Context, Poll};struct MyFuture {// 可以包含一些状态
}impl Future for MyFuture {type Output = u32;fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {// 这里可以安全地访问固定的内存Poll::Ready(42)}
}fn main() {let mut my_future = MyFuture {};let mut pinned_future = Box::pin(my_future);let waker = /* 创建或获取一个 waker */;let mut cx = Context::from_waker(&waker);let output = pinned_future.as_mut().poll(&mut cx);println!("Output: {:?}", output);
}

在这个例子中,我们创建了一个 MyFuture 结构体,并将其固定在堆上,确保它在异步任务执行期间不会被移动。

2. 异步编程中的 Unpin

在异步编程中,Unpin 的作用是允许某些 Future 被自由地移动。这对于那些不需要固定在内存中的 Future 来说是非常有用的。例如:

struct MyUnpinFuture {data: String,
}impl Future for MyUnpinFuture {type Output = u32;fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {// 这里可以安全地访问固定的内存Poll::Ready(42)}
}impl Unpin for MyUnpinFuture {}fn main() {let mut my_future = MyUnpinFuture { data: String::from("Unpin") };let mut pinned_future = Box::pin(my_future);let waker = /* 创建或获取一个 waker */;let mut cx = Context::from_waker(&waker);let output = pinned_future.as_mut().poll(&mut cx);println!("Output: {:?}", output);
}

在这个例子中,MyUnpinFuture 实现了 Unpin,因此它可以在内存中自由移动,即使它被包装在 Pin 中。

四、总结

通过本文的讲解,我们了解了 PinUnpin 在 Rust 中的重要性及其实际应用。Pin 通过防止对象被移动来保证内存安全,而 Unpin 则提供了一种灵活的方式来控制哪些类型可以被移动。理解并正确使用这两个概念,对于编写高效、安全的异步代码尤为重要。

在实际开发中,PinUnpin 的使用可能会涉及到一些复杂的场景,但只要掌握了它们的基本原理和使用方法,就能够灵活地应对各种情况。希望本文的介绍能够帮助你更好地理解和使用这两个概念,让你的 Rust 程序更加安全、高效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/79716.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/79716.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何界定合法收集数据?

首席数据官高鹏律师团队 在当今数字化时代&#xff0c;数据的价值日益凸显&#xff0c;而合法收集数据成为了企业、机构以及各类组织必须严守的关键准则。作为律师&#xff0c;深入理解并准确界定合法收集数据的范畴&#xff0c;对于保障各方权益、维护法律秩序至关重要。 一…

自动驾驶的“眼睛”:用Python构建智能障碍物检测系统

自动驾驶的“眼睛”:用Python构建智能障碍物检测系统 在自动驾驶技术日益成熟的今天,障碍物检测系统成了汽车智能化不可或缺的部分。无论是高速公路上的突发状况,还是城市街道中的行人与车辆,准确识别障碍物并及时反应,是保证行车安全的关键。 那么,我们如何用Python构…

19.Excel数据透视表:第2部分数据透视计算

一 日期组合 不想看具体是哪一天的收入&#xff0c;想看每个月的收入是多少&#xff0c;要对日期进行组合。 光标选中日期字段下的数据&#xff0c; 右键。 补充&#xff1a;第2种方法。 补充&#xff1a;可以同时选择多个。 下面这个是错误的。 源数据里面有不同的年份&#x…

Eclipse 插件开发 6 右键菜单

Eclipse 插件开发 6 右键菜单 1 plugin.xml2 SampleHandler.java3 Activator.java 1 plugin.xml <?xml version"1.0" encoding"UTF-8"?> <?eclipse version"3.4"?> <plugin><!-- 定义命令 --><extension point&…

用vite脚手架建立 前端工程

​ 参考 开始 | Vite 官方中文文档 脚本 chcp 65001 echo 建立vite工程 set PRO_NAMEmy-vue-app call npm create vitelatest %PRO_NAME% --template vue cd ./%PRO_NAME%set NOW_PATH%cd% echo now_path %NOW_PATH% echo 点击回车启动vite工程&#xff0c;请访问ht…

ESP32C3连接wifi

文章目录 &#x1f527; 一、ESP32-C3 连接 Wi-Fi 的基本原理&#xff08;STA 模式&#xff09;✅ 二、完整代码 注释讲解&#xff08;适配 ESP32-C3&#xff09;&#x1f4cc; 三、几个关键点解释&#x1f51a; 四、小结 &#x1f527; 一、ESP32-C3 连接 Wi-Fi 的基本原理&a…

LangSmith 基本使用教程

LangSmith 是一个强大的工具&#xff0c;可以帮助开发者追踪、监控和分析语言模型应用程序的性能。下面我将介绍两种基本的追踪方式&#xff1a;追踪 OpenAI 调用和追踪整个应用程序。 1. 追踪 OpenAI 调用 (Trace OpenAI calls) 这种方法主要用于追踪对 OpenAI API 的调用&a…

Python基础学习-Day23

目录 基础概念转换器&#xff08;transformer&#xff09;估计器&#xff08;estimator&#xff09;管道&#xff08;pipeline&#xff09; 实例pipeline 基础概念 pipeline在机器学习领域可以翻译为“管道”&#xff0c;也可以翻译为“流水线”&#xff0c;是机器学习中一个重…

相对论速度叠加公式与双曲正切

复习下相对论速度叠加公式吧&#xff0c;物理&#xff0c;是不是很多人都忘了呀。假设速度为 u , v u,v u,v&#xff0c;那么叠加后的速度 w w w为&#xff1a; w u v 1 u v / c 2 w\frac{uv}{1uv/c^2} w1uv/c2uv​   这个公式告诉我们&#xff0c;在一个速度为2/3光速的…

【前缀和】和为 K 的子数组(medium)

【前缀和】和为 K 的子数组 题目描述算法原理和细节问题代码 题目描述 和为 K 的子数组 给定一个整数数组和一个整数 k &#xff0c;请找到该数组中和为 k 的连续子数组的个数。 示例 1&#xff1a; 输入:nums [1,1,1], k 2 输出: 2 解释: 此题 [1,1] 与 [1,1] 为两种不同的…

在Ubuntu服务器上部署Label Studio

一、拉取镜像 docker pull heartexlabs/label-studio:latest 二、启动容器 &#xff08;回到用户目录&#xff0c;例&#xff1a;输入pwd&#xff0c;显示 /home/<user>&#xff09; docker run -d --name label-studio -it -p 8081:8080 -v $(pwd)/mydata:/label-st…

MySQL 从入门到精通(三):日志管理详解 —— 从排错到恢复的核心利器

在 MySQL 数据库的日常运维中&#xff0c;日志是定位问题、优化性能、数据恢复的核心工具。无论是排查服务器启动异常&#xff0c;还是分析慢查询瓶颈&#xff0c;亦或是通过二进制日志恢复误删数据&#xff0c;日志都扮演着 “数据库黑匣子” 的角色。本文将深入解析 MySQL 的…

内存中的“BANK”

一、BANK的定义与物理结构 基本概念 BANK&#xff08;存储体&#xff09; 是内存芯片内部的一个逻辑或物理分区&#xff0c;每个BANK由存储单元阵列、地址解码电路和缓冲器组成&#xff0c;用于分块管理内存操作。 作用&#xff1a;通过并行操作减少访问冲突&#xff0c;提升内…

机器学习——聚类算法练习题

一、 随机创建不同二维数据集作为训练集 &#xff0c;并结合k-means算法将其聚类 &#xff0c;你可以尝试分别聚类不同数量的簇 &#xff0c;并观察聚类 效果&#xff1a; 聚类参数n_cluster传值不同 &#xff0c;得到的聚类结果不同 代码展示&#xff1a; from sklearn.da…

kafka----初步安装与配置

目录标题 ⭐kafka 与 zookeeper间的关系一.集群部署二.修改配置文件三.分发安装包四.启动与关闭 kafka 与 zookeeper 相同&#xff0c;是以集群的形式使用 ⭐kafka 与 zookeeper间的关系 kafka 的使用 要在 zookeeper 集群配置好的基础上 使用要想启动kafka 要先启动 zookeep…

进程与线程:07 CPU调度策略

一、课程内容概述 本节课程主要讲解操作系统的CPU调度策略&#xff0c;聚焦于基本操作系统上的调度算法&#xff0c;探讨其大致实现方式、需折中考虑的问题。CPU调度在不同场景下复杂程度不同&#xff0c;如卫星、导弹等实时性要求高的系统&#xff0c;需采用实时调度&#xf…

JPG与PDF格式转换器

该插件可实现JPG与PDF格式的互转。 MainForm.Designer.cs using System.Windows.Forms; namespace JpgToPdfConverter {partial class MainForm{private System.ComponentModel.IContainer components null;protected override void Dispose(bool disposing){if (disposing &…

LlamaIndex 第八篇 MilvusVectorStore

本指南演示了如何使用 LlamaIndex 和 Milvus 构建一个检索增强生成&#xff08;RAG&#xff09;系统。 RAG 系统将检索系统与生成模型相结合&#xff0c;根据给定的提示生成新的文本。该系统首先使用 Milvus 等向量相似性搜索引擎从语料库中检索相关文档&#xff0c;然后使用生…

浅聊一下数据库的索引优化

背景 这里的索引说的是关系数据库&#xff08;MSSQL&#xff09;中的索引。 本篇不是纯技术性的内容&#xff0c;只是聊一次性能调优的经历&#xff0c;包含到一些粗浅的实现和验证手段&#xff0c;所以&#xff0c;大神忽略即可。 额…对了&#xff0c;笔者对数据库的优化手段…

【android bluetooth 框架分析 02】【Module详解 7】【VendorSpecificEventManager 模块介绍】

1. 背景 我们在 gd_shim_module 介绍章节中&#xff0c;看到 我们将 VendorSpecificEventManager 模块加入到了 modules 中。 // system/main/shim/stack.cc modules.add<hci::VendorSpecificEventManager>();在 ModuleRegistry::Start 函数中我们对 加入的所有 module…