4.Rust中的所有权(Rust成名绝技)

Rust成名绝技

Rust 之所以能成为万众瞩目的语言,就是因为其内存安全性。在以往,内存安全几乎都是通过 GC 的方式实现,但是 GC 会引来性能、内存占用以及全停顿等问题,在高性能场景、实时性要求高和系统编程上是不可接受的,因此 Rust 采用了所有权系统。这也是Rust核心、如果不理解所有权Rust就无法深入学习下去。

内存管理方案

所有的程序都必须和计算机内存打交道,如何从内存中申请空间来存放程序的运行内容,如何在不需要的时候释放这些空间,成了重中之重,也是所有编程语言设计的难点之一。

手动管理

所有程序都需要和计算机内存打交道,学过C语言的都知道,C语言中我们需要手动管理内存空间。

int main() {
    int size;
    int *array;
    printf("Enter the size of the array: ");
    scanf("%d", &size);
    // 动态分配内存
    array = (int *)malloc(size * sizeof(int));
    if (array == NULL) {
        printf("Memory allocation failed.\n");
        return 1;
    }
    // 释放内存
    free(array);
    return 0;
}

手动管理内存会有什么问题呢?

  • 内存泄漏:如果你分配了内存但没有显式释放,就会导致内存泄漏。内存泄漏会逐渐消耗可用内存,最终可能导致程序崩溃或运行缓慢。
  • 悬空指针:如果你释放了一块内存,但后续仍然使用指向该内存的指针,就会导致悬挂指针。使用悬挂指针可能会导致未定义的行为,包括访问无效内存或者覆盖其他数据。
  • 内存访问错误:手动分配内存时,需要确保不会越界访问分配的内存块。如果越界访问数组或者在访问已释放的内存,可能会导致程序崩溃或产生不可预测的结果。
  • 多次释放:如果你多次释放同一块内存,会导致内存错误,可能会导致程序崩溃或者破坏其他数据。
  • 内存碎片:频繁地分配和释放内存可能会导致内存碎片化,即内存空间被分割成多个小块,无法有效利用。这可能会降低程序的性能。

GC(garbage collection)垃圾回收

采用GC的代表性语言:Java、Go、Js。
这里以JS为例可以看我的这篇文章。

通过所有权来管理内存

代表语言Rust,这种方式,利用编译器在编译时进行规则检查,不会造成运行时的性能损耗,解下来我们详细介绍所有权的概念。

栈和堆是编程语言最核心的数据结构,但是在很多语言中,你并不需要深入了解栈与堆。 但对于 Rust 这样的系统编程语言,值是位于栈上还是堆上非常重要, 因为这会影响程序的行为和性能。

栈和堆的核心目标就是为程序在运行时提供可供使用的内存空间。

栈按照顺序存储值并以相反顺序取出值,这也被称作后进先出。想象一下一叠盘子:当增加更多盘子时,把它们放在盘子堆的顶部,当需要盘子时,再从顶部拿走。不能从中间也不能从底部增加或拿走盘子!

增加数据叫做进栈,移出数据则叫做出栈。

因为上述的实现方式,栈中的所有数据都必须占用已知且固定大小的内存空间,假设数据大小是未知的,那么在取出数据时,你将无法取到你想要的数据。

与栈不同,对于大小未知或者可能变化的数据,我们需要将它存储在堆上。

当向堆上放入数据时,需要请求一定大小的内存空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的指针, 该过程被称为在堆上分配内存,有时简称为 “分配”(allocating)。

接着,该指针会被推入栈中,因为指针的大小是已知且固定的,在后续使用过程中,你将通过栈中的指针,来获取数据在堆上的实际内存位置,进而访问该数据。

由上可知,堆是一种缺乏组织的数据结构。想象一下去餐馆就座吃饭: 进入餐馆,告知服务员有几个人,然后服务员找到一个够大的空桌子(堆上分配的内存空间)并领你们过去。如果有人来迟了,他们也可以通过桌号(栈上的指针)来找到你们坐在哪。

性能区别

在栈上分配内存比在堆上分配内存要快,因为入栈时操作系统无需进行函数调用(或更慢的系统调用)来分配新的空间,只需要将新数据放入栈顶即可。相比之下,在堆上分配内存则需要更多的工作,这是因为操作系统必须首先找到一块足够存放数据的内存空间,接着做一些记录为下一次分配做准备,如果当前进程分配的内存页不足时,还需要进行系统调用来申请更多内存。 因此,处理器在栈上分配数据会比在堆上分配数据更加高效。

所有权与堆栈

当你的代码调用一个函数时,传递给函数的参数(包括可能指向堆上数据的指针和函数的局部变量)依次被压入栈中,当函数调用结束时,这些值将被从栈中按照相反的顺序依次移除。

因为堆上的数据缺乏组织,因此跟踪这些数据何时分配和释放是非常重要的,否则堆上的数据将产生内存泄漏 —— 这些数据将永远无法被回收。这就是 Rust 所有权系统为我们提供的强大保障。

对于其他很多编程语言,你确实无需理解堆栈的原理,但是在 Rust 中,明白堆栈的原理,对于我们理解所有权的工作原理会有很大的帮助。

所有权

所有权规则:

  1. 每一个值同时只能被一个变量拥有。(值就像钞票,只能被一个人持有,毕竟钞票带编号没有相同的)
  2. 当所有者离开作用域范围,该值将被丢弃。
fn main(){
	let x = 1; // 1的所有权被x持有,x就是所有者。 
}

拷贝Copy trait

关于trait后面会介绍~。
在 Rust 中,实现了 Copy trait 的类型是可复制的。Copy trait 表示类型的值可以通过简单的位拷贝来复制,而不会发生所有权转移。当一个变量的值被复制到另一个变量时,原始变量仍然保留对其值的所有权。
以下是 Rust 标准库中一些常见的实现了 Copy trait 的类型:

  • 所有的整数类型(如 i32、u64 等)
  • 所有的浮点数类型(如 f32、f64 等)
  • bool 类型
  • 字符类型 char
  • 元组,只有当元组的所有元素都是可复制的时候才能拷贝
	let x = 1;
	let y = x;// 拷贝 x 的值到 y,x 仍然保留其所有权
	println!(x) // 1 

可能有同学会有疑问:这种拷贝不消耗性能吗?实际上,这种栈上的数据足够简单,而且拷贝非常非常快,只需要复制一个整数大小(i32,4 个字节)的内存即可,因此在这种情况下,拷贝的速度远比在堆上创建内存来得快的多。

所有权转移

当变量离开作用域后,Rust 会自动调用 drop 函数并清理变量的堆内存。不过由于两个 String 变量指向了同一位置。这就有了一个问题:当 s1 和 s2 离开作用域,它们都会尝试释放相同的内存。这是一个叫做 二次释放(double free) 的错误,也是之前提到过的内存安全性 BUG 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
因此,Rust 这样解决问题:当 s1 被赋予 s2 后,Rust 认为 s1 不再有效,因此也无需在 s1 离开作用域后 drop 任何东西,这就是把所有权从 s1 转移给了 s2,s1 在被赋予 s2 后就马上失效了。
Rust对于实现了 Drop trait 的类型是不可复制的,因为它们可能会具有特殊的资源释放行为。当一个不可复制的类型的值被赋值给另一个变量时,所有权会被移动,而不是发生拷贝。例如:

	let s1 = String::from("hello Rust");
	let s2 = s1;
	println!("s1:{},s2:{}",s1,s2);

在这里插入图片描述
可以看到报错所有权已经转移,他没有实现Copy trait.

函数参数和返回值岁有权转移示例:

fn main() {
    let s = String::from("hello");  // s 进入作用域

    takes_ownership(s);             // s 的值移动到函数里 ...
    println!(s)                     // ... 所以到这里不再有效会报错

    let x = 5;                      // x 进入作用域

    makes_copy(x);                  // x 应该移动函数里,
                                    // 但 i32 是 Copy 的,所以在后面可继续使用 x

} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
  // 所以不会有特殊操作

fn takes_ownership(some_string: String) { // some_string 进入作用域
    println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放

fn makes_copy(some_integer: i32) { // some_integer 进入作用域
    println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

seo蜘蛛池的概念!蚂蚁SEO

蜘蛛池是一种特殊的网络营销技术,它的主要作用是吸引搜索引擎爬虫,提高网站的收录和排名,从而增加网站的流量和曝光度。 蚂蚁SEO是一个SEO工具,可以帮助您提高网站权重,吸引更多的搜索引擎爬虫,提高网站的…

idea2023版使用废弃类没有删除线处理方法

idea2023版使用废弃类没有删除线处理方法 新版Idea使用废弃类时,默认是黄色警告处理方法1. 打开file -> setting2. 编辑(Editor) -> 检查(Inspections) -> 搜索Deprecated API usage 新版Idea使用废弃类时,默认是黄色警告 处理方法 1. 打开file -> setting 2. 编…

Igraph入门指南 2

3、图的基本要素——边(Edge|Arc) 图的最本质的内容是一种二元关系,如果给这种二元关系赋予一个方向,就产生了有向图和无向图的分类,在教材中,无向的边叫Edge,有向的边叫Arc,另外,根据两个顶点…

【数据集】MSWEP(多源加权集合降水)再分析数据

MSWEP全球降水数据 数据概述数据下载参考数据概述 MSWEP(Multi-Source Weighted-Ensemble Precipitation)降水数据集是一种高分辨率、全球覆盖的降水数据产品,它融合了多种来源的降水信息,包括卫星遥感数据、雷达观测、地面气象站观测数据以及数值天气预报模型的输出。MSW…

Java基于微信小程序的校园失物招领小程序

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…

超分辨率(1)--基于GAN网络实现图像超分辨率重建

目录 一.项目介绍 二.项目流程详解 2.1.数据加载与配置 2.2.构建生成网络 2.3.构建判别网络 2.4.VGG特征提取网络 2.5.损失函数 三.完整代码 四.数据集 五.测试网络 一.项目介绍 超分辨率(Super-Resolution),简称超分&#xff08…

智慧安防视频远程监控平台EasyCVR集成后播放只有一帧画面是什么原因?

智慧安防视频监控平台EasyCVR能在复杂的网络环境中(专网、局域网、广域网、VPN、公网等)将前端海量的设备进行统一集中接入与视频汇聚管理,平台可支持的接入协议包括:国标GB28181、RTSP/Onvif、RTMP,以及厂家的私有协议…

利用GPT开发应用002:Transformer架构及其在LLMs中的作用

文章目录 一、交叉注意力(cross-attention)二、自注意力(self-attention)三、Transformer优势四、Transformer组件五、LLMs演变过程 Transformer架构彻底改变了自然语言处理。它大量采用了名为交叉注意力(cross-attent…

java SSM二手交易网站系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM厂房管理系统是一套完善的web设计系统(系统采用SSM框架进行设计开发,springspringMVCmybatis),对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S…

【TensorFlow】P1 Google Colab 使用

目录 访问 Google Colab快速切换 (文本/脚本)构建 Google 云开发生态Colab 支持运行 bash 脚本下载 Colab 代码文件为 .py .ipynb 访问 Google Colab Google Colab 需要科学上网,如何科学上网不多赘述。 Google Colab 网址:https://colab.research.goo…

Java面试篇【RabbitMQ】常见面试题(2024最新)

RabbitMQ 1.为什么使用MQ?优点是什么 因为MQ可以异步处理,提高系统吞吐量。 应用解耦,系统之间可以通过消息通信,不关心其他系统的处理。 流量削峰,可以通过消息队列的长度,控制请求量。可以缓解短时间内的高并发请…

Docker的镜像操作

目录 镜像的操作(**开头为常用请留意) 镜像查找 **拉取镜像 **推送镜像 **查看镜像 **修改镜像名称 **查看镜像的详细信息 ​编辑 删除镜像 查看所有镜像ID 删除全部镜像 **查看镜像的操作历史 镜像迁移 镜像打包 远程发送镜像(需要先打包) 本地镜像tar包恢复 镜像过…

【解决方案】腾讯云:对象存储创建存储桶并上传文件后访问对象 url 时文件直接触发下载的问题

大半夜搞服务器后台设置对象存储的时候碰到的问题,看了下文档然后解决了,所以就顺手记录一下。 0x00 问题 & 解决方案 ❓ 问题描述:腾讯云对象存储创建存储桶并上传文件,此时浏览器访问对象 url 时文件时会自动下载该文件&am…

Intel CPU体系结构

原文来自一文解析,Linux内核——Intel CPU体系结构 本文主要介绍Intel CPU体系结构,以供读者能够理解该技术的定义、原理、应用。 🎬个人简介:一个全栈工程师的升级之路! 📋个人专栏:计算机杂记…

three.js如何实现简易3D机房?(三)显示信息弹框/标签

接上一篇: three.js如何实现简易3D机房?(二)模型加载的过渡动画:http://t.csdnimg.cn/onbWY 目录 七、创建信息展示弹框 1.整体思路 (1)需求: (2)思路:…

110. 平衡二叉树【简单】

110. 平衡二叉树【简单】 题目描述: 给定一个二叉树,判断它是否是高度平衡的二叉树。 本题中,一棵高度平衡二叉树定义为: 一个二叉树每个节点的左右两个子树的高度差的绝对值不超过 1 。 示例 1: 输入:r…

Ubuntu 下使用 Pybind11 实现 C++ 调用 Python 接口的示例

Pybind11 是一个轻量级的库,它提供了在 C 中无缝集成 Python 代码的能力。使用 Pybind11,你可以很容易地从 C 调用 Python 代码,反之亦然。下面我将通过一个简单的例子来展示如何在 Ubuntu 系统上使用 Pybind11 从 C 调用 Python 接口。 安装…

Skywalking官方的实战模拟项目Live-Demo

Skywalking 官方的实战模拟项目Live-Demo Live-Demo 是 Skywalking 官方的实战模拟项目,其中包含4个子模块项目 projectA访问projectB、projectC两个SpringBoot项目 projectB访问本地的H2数据库 projectC访问www.baidu.com并同时向一台Kafka消息队列写入数据 proje…

【C语言】冒泡排序

概念 冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地遍历要排序的列表,一次比较两个元素,并且如果它们的顺序错误就把它们交换过来。通过多次的遍历和比较,最大(或最小)的元素…

Bee Mobile组件库重磅升级

Bee Mobile组件库重磅升级! 丰富强大的组件移动预览快速上手create-bee-mobile Bee Mobile组件库重磅升级! Bee Mobile组件库最新 v1.0.0 版本,支持最新的 React v18。 主页:Bee Mobile 丰富强大的组件 一共拥有50多个组件&…