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

news2024/11/24 5:05:03

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 移出作用域。不会有特殊操作

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

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

相关文章

代码随想录-贪心算法

贪心的本质是选择每一阶段的局部最优,从而达到全局最优。 455. 分发饼干 class Solution {public int findContentChildren(int[] g, int[] s) {Arrays.sort(g);Arrays.sort(s);int count 0;int sIndex s.length - 1;for (int i g.length - 1; i > 0; i--) …

Mamba模型底层技术详解,与Transformer到底有何不同?

导读: 基于状态空间模型(State Space Model)的Mamba模型最近在深度学习领域有赶超Transformer的势头。其最主要的优势就在于其在长序列任务上的优异性能与较低的计算复杂度。本文就Mamba模型的原理进行解析,分析Mamba模型在结构上…

Dynamo初学常识梳理(四)——Revit图元

希望想学 Dynamo 的小伙伴坚持住,每天积累一点点知识,Dynamo 你很快就能上手的!Dynamo 并不是你想的那样难学哦! 今天要讲的是如何从 Dynamo 中获取 Revit 的图元,这些节点很常用,不需要全背下来&#xff0…

田宏斌:以人为本的听力健康管理实践经验 | 演讲嘉宾公布

一、助辅听器材Ⅲ分论坛 助辅听器材Ⅲ分论坛将于3月28日同期举办! 听力贯穿人的一生,听觉在生命的各个阶段都是至关重要的功能,听力问题一旦出现,会严重影响生活质量。助辅听器材能有效提高生活品质。在这里,我们将分享…

【MybatisPlus】QueryWrapper、UpdateWrappe、LambdaQueryWrapper、LambdaUpdateWrapper

一、Wrapper简介 QueryWrapper、UpdateWrapper、LambdaQueryWrapper 和 LambdaUpdateWrapper 都是 MyBatis-Plus 框架中用于构建条件的工具类,它们之间的关系是继承关系。其中 QueryWrapper 和 UpdateWrapper 是基于普通的对象属性名来构建条件的,而 La…

2024最新Android大厂面试真题大全,推荐学习

历时半年,我们终于整理出了这份市面上最全面的最新Android面试题解析大全! 章节目录 第一章:Android 基础 面试题 第二章:Android 高级 面试题 第三章:开源框架实战面试解析 第四章:Java 面试题 第五章&a…

SpringBoot源码解读与原理分析(三)条件装配

文章目录 2.3 Spring Framework的条件装配2.3.1 基于Profile的装配1.Profile源码解读2.使用Profile注解(3)命令行参数配置Profile3.Profile运用于实际开发4.Profile的不足 2.3.2 基于Conditional的装配1.Conditional源码解读2.Conditional使用3.Conditio…

Kosmos-1: 通用接口架构下的多模态大语言模型

Kosmos-1: 通用接口架构下的多模态大语言模型 FesianXu 20230513 at Baidu Search Team 前言 在大规模语言模型(Large Language Model, LLM)看似要带来新一番人工智能变革浪潮之际,越来越多尝试以LLM作为通用接口去融入各种任务的工作&#…

WordPress建站入门教程:小皮面板phpstudy如何安装PHP和切换php版本?

小皮面板phpstudy支持的PHP版本有很多,包括5.2.17、5.3.29、5.4.45、5.5.9、5.6.9、7.0.9、7.1.9、7.2.9、7.3.4、7.3.9、7.4.3、8.0.2、8.2.9。那么我们如何安装其他的php版本和切换网站的php版本呢?只需要简单几步即可,具体如下&#xff1a…

JavaWeb - 2 - HTML、CSS

什么是HTML、CSS? HTML(HyperText Markup Language):超文本标记语言 超文本:超越了文本的限制,比普通文本更强大,除了文字信息,还可以定义图片、音频、视频等内容 标记语言&…

不用下载的工具却能保存西瓜视频的原画视频,支持无水印!

近年来,西瓜视频可谓是炙手可热,得益于其强大的后盾——抖音,以及推出的"中视频计划"。这个计划慷慨地斥资20亿用于支持视频制作者,因此在西瓜视频平台上,我们目睹了大量优质的长视频如雨后春笋般涌现。 对于…

SpringCloud 各自组件的停更/升级/替换

一、停更不停用 现在 SpringCloud 不再修复 bug,也不再接收合并请求,也不再发布新版本,但是目前还是可以继续使用的。 二、以前的组件 以前 SpringCloud 常用的组件如下图,服务的注册和发现使用 Eureka,服务的负载和调…

工业镜头的重要参数之视场、放大倍率、芯片尺寸--51camera

今天来简单介绍下工业镜头中常用的参数中的三个: 1、视场 视场(FOV)也称视野,是指能被视觉系统观察到的物方可视范围。 对于镜头而言,可观察到的视场跟镜头放大倍率及相机芯片选择有关。因此需要根据被观察物体的尺寸&#xff…

threejs展示glb模型

原模型为rvt模型 <template><div ref"threeJsContainer"class"three-js-container"></div> </template> <script> import { defineComponent } from "vue"; import * as THREE from "three"; import…

如何选择阿里云服务器配置,过来人说说

阿里云服务器配置怎么选择&#xff1f;CPU内存、公网带宽和系统盘怎么选择&#xff1f;个人开发者或中小企业选择轻量应用服务器、ECS经济型e实例&#xff0c;企业用户选择ECS通用算力型u1云服务器、ECS计算型c7、通用型g7云服务器&#xff0c;阿里云服务器网aliyunfuwuqi.com整…

C++核心编程之内存分区模型,引用,函数提高

1&#xff0c;类型分区模型 c程序在执行中&#xff0c;将内存大方向划分为4个区域 1&#xff0c;代码区&#xff1a;存放函数体的二进制代码&#xff0c;由操作系统进行管理的 2&#xff0c;全局区&#xff1a;存放全局变量和静态变量以及常量 3&#xff0c;栈区&#xff1…

【学习笔记】java项目:黑马头条(day01)

文章目录 环境搭建、SpringCloud微服务(注册发现、服务调用、网关)1)课程对比2)项目概述2.1)能让你收获什么2.2)项目课程大纲2.3)项目概述2.4)项目术语2.5)业务说明 3)技术栈4)nacos环境搭建4.1)虚拟机镜像准备4.2)nacos安装 5)初始工程搭建5.1)环境准备5.2)主体结构 6)登录6.1…

Openfeign+Ribbon+Hystrix断路器(服务降级)

热部署对于Hystrix的热不是不是很明显 所以最好修改代码之后重启服务 简介 在微服务架构中存在多个可直接调用的服务,这些服务若在调用时出现故障会导致连锁效应,也就是可能让整个系统变得不可用,这种情况我们称之为服务雪崩效应. 服务雪崩效应通常发生在微服务架构中&…

应用监控 eBPF 版:实现 Golang 微服务的无侵入应用监控

作者&#xff1a;古琦 在现代软件架构中&#xff0c;微服务已成为构建可扩展和灵活应用程序的流行方式。每个微服务负责应用程序的一部分功能&#xff0c;它们共同工作以提供完整的服务。由于微服务架构的分散特性&#xff0c;监控变得至关重要&#xff0c;有效的微服务监控是…

缺陷检测:使用PatchCore训练自己的数据集

文章目录 前期准备两种方法 演示运行结果 代码详解见缺陷检测–PatchCore的代码解读 前期准备 必须包含有训练图片&#xff08;无缺陷图片&#xff09;、测试图片&#xff08;缺陷图片&#xff09;和ground_truth&#xff0c;并且ground_truth必须与对应图片的名称相同。 本文…