[译]理解 Rust 中的所有权

news2024/10/5 15:28:13

本文译者为 360 奇舞团前端开发工程师

原文标题:Understanding ownership in Rust

原文作者:Ukpai Ugochi

原文链接:https://blog.logrocket.com/understanding-ownership-in-rust/


在 Stack Overflow 进行的开发人员调查中,Rust 连续第五年成为最受欢迎的编程语言。开发者喜爱 Rust 的原因有很多,其中之一就是它的内存安全保证。

Rust 通过称为所有权的特性来保证内存安全。所有权与其他语言中的垃圾收集器的工作方式不同,因为它只包含编译器需要在编译时检查的一组规则。如果不遵守所有权规则,编译器将不会编译。borrow checker确保你的代码遵循所有权规则。

对于没有垃圾收集器的语言,你需要显式分配和释放内存空间。当涉及大型代码库时,这很快就会变得乏味和具有挑战性。

值得庆幸的是, 内存管理由 Rust 编译器使用所有权模型处理。Rust编译器会自动插入一个 drop 语句来释放内存。它使用所有权模型来决定在哪里释放内存;当所有者超出范围时,内存将被释放。

fn main() {
    {
    let x = 5 ;
    // x 被丢弃在这里,因为它超出了范围
    }
}

什么是栈和堆?

栈和堆都是可供你的代码在运行时使用的内存存储段。对于大多数编程语言,开发人员通常不关心栈和堆上的内存分配情况。但是,由于 Rust 是一种系统编程语言,因此值的存储方式(在栈或堆中)对于语言的行为方式至关重要。

内存是如何存储在栈中的呢?假设桌子上有一叠书,这些书的排列方式是最后一本书放在书堆的顶部,第一本书在底部。理想情况下,我们不想从书堆下面滑出最下面的书,从上面挑一本书来阅读会更容易。

这正是内存在栈中的存储方式;它使用后进先出的方法。在这里,它按照获取值的顺序存储值,但以相反的顺序删除它们。同样重要的是要注意存储在栈中的所有数据在编译时都具有已知大小。

堆中的内存分配与栈中的内存分配方式不同。假设你要为朋友买一件衬衫,但是你不知道朋友穿的衬衫具体尺码,但经常看到他,你认为他可能是M码或L码。虽然你不完全确定,但你购买大号是因为即使他是中号,你的朋友仍然能穿上它。这是栈和堆之间的一个重要区别:我们不需要知道存储在堆中的值的确切大小。

与栈相比,堆中没有组织。将数据推入和推出栈很容易,因为一切都是有组织的并遵循特定的顺序。系统理解,当你将一个值压入栈时,它会停留在顶部,而当你需要从栈中取出一个值时,你正在检索存储的最后一个值。

然而,堆中的情况并非如此。在堆上分配内存需要动态地搜索一块足够大的内存空间来满足分配要求,并且返回指向该内存位置的地址。检索值时,需要使用指针来找到存储该值的内存位置。

在堆上分配看起来像书籍索引,其中存储在堆中的值的指针存储在栈中。但是,分配器还需要搜索一个足够大的空白空间来包含该值。

函数的局部变量存储在函数栈中,而数据类型(如String、Vector、Box等)存储在堆中。了解 Rust 的内存管理以确保应用程序按预期运行非常重要。

所有权规则

所有权有三个基本规则来预测内存如何存储在栈和堆中:

1.每个 Rust 值都有一个称为其“所有者”的变量:

let x = 5 ; // x is the owner of the value "5"

2.每个值一次只能有一个所有者

3.当所有者超出范围时,该值将被删除:

fn main () { 
    { // // scope begins
        let s = String :: from ( "hello" ); // s comes into scope
    }  
    // the value of s is dropped at this point, it is out of scope
}

所有权如何运作

在我们的介绍中,我们建立了一个事实,即所有权不像垃圾收集器系统。大多数编程语言要么使用垃圾收集器,要么要求开发人员自己分配和释放内存。

在所有权中,我们为自己请求内存,当所有者超出范围时,该值将被删除并释放内存。这正是所有权规则第三条所解释的。为了更好地理解这是如何工作的,让我们看一个例子:

fn main () {
    { 
        // a is not valid here
        let a = 5 ; // a is valid here
        // do stuff with a
    } 
    println!("{}", a) // a is no longer valid at this point, it is out of scope
}

这个例子非常简单;这就是栈中内存分配的工作原理。a由于我们知道它的值将占用的确切空间,因此在栈上分配了一块内存 ( ) 5。然而,情况并非总是如此。有时,你需要为一个在编译时不知道其大小的可增长值分配内存空间。

对于这种情况,内存是在堆上分配的,你首先必须请求内存,如下例所示:

fn main () { 
    { 
        let mut s = String :: from ( "hello" ); // s is valid from this point forward
        push_str ( ", world!" ); // push_str() appends a literal to a String
    	println !( "{}" , s ); // This will print `hello, world!`
    } 
    // s is no longer valid here
}

我们可以根据需要附加任意多的字符串,s因为它是可变的,因此很难知道编译时所需的确切大小。因此在我们的程序中我们需要一个字符串大小的内存空间:

克隆和复制

在本节中,我们将研究所有权如何影响 Rust 中的某些功能,从clone和copy功能开始。

对于具有已知大小的值(如 )integers,将值复制到另一个值会更容易。例如:_

fn main() {
    let a = "5" ; 
    let b = a ; // copy the value a into b
    println !( "{}" , a ) // 5 
    println !( "{}" , b ) // 5 
}

因为a存储在栈中,所以更容易复制它的值来为 制作另一个副本b。对于存储在堆中的值,情况并非如此:

fn main () { 
    let a = String :: from ( "hello" ); 
    let b = a ; // copy the value a into b
    println !( "{}" , a ) // This will throw an error because a has been moved or ownership has been transferred
    println !( "{}" , b ) // hello 
}

当你运行该命令时,你将收到一个错误 error[E0382]: borrow of moved value: "a"move

所有权和函数

将值传递给函数遵循相同的所有权规则,这意味着它们一次只能有一个所有者,并且一旦超出范围就释放内存。让我们看看这个例子:

fn main() {
    let s1 = givesOwnership(); // givesOwnership moves its return

    let s2 = String::from("hello"); // s2 comes into scope
    let s3 = takesAndGivesBack(s2); // s2 is moved into takesAndGivesBack, 
                                    // which also moves its return value into s3

} // Here, s3 goes out of scope and is dropped. s2 goes out of scope but was
  // moved, so nothing happens. s1 goes out of scope and is dropped.


fn givesOwnership() -> String { // givesOwnership will move its
                                // return value into the function
                                // that calls it

    let someString = String::from("hello");  // someString comes into scope

    someString                               // someString is returned and
                                             // moves out to the calling
                                             // function
}

// takesAndGivesBack will take a String and return one
fn takesAndGivesBack(aString: String) -> String { // aString comes into
                                                      // scope

    aString  // aString is returned and moves out to the calling function
}

切片

引用序列中彼此相邻的元素,而不是引用整个集合。因此,可以使用切片类型。但是,此功能不具有引用和借用之类的所有权。

让我们看看下面的例子。在此示例中,我们将使用切片类型来引用连续序列中值的元素:

fn main() {
    let s = String::from("Nigerian");
    // &str type
    let a = &s[0..4];// doesn't transfer ownership, but references/borrow the first four letters.
    let b = &s[4..8]; // doesn't transfer ownership, but references/borrow the last four letters.
    println!("{}", a); // prints Nige

    println!("{}", b); // prints rian
    
    let v=vec![1,2,3,4,5,6,7,8];

    // &[T] type
    let a = &v[0..4]; // doesn't transfer ownership, but references/borrow the first four element.
    let b = &v[4..8]; // doesn't transfer ownership, but references/borrow the last four element.
    println!("{:?}", a); // prints [1, 2, 3, 4]
    println!("{:?}", b); // prints [5, 6, 7, 8]
    
}

结论

所有权是 Rust 的一个重要特性。掌握所有权的概念,有利于编写可扩展的代码。很多人喜欢 Rust 的原因就是因为这个特性,一旦你掌握了它,你就可以更高效的编写代码。

在本文中,我们介绍了所有权的基础知识及其规则,以及如何应用它们。除此之外,还介绍了 Rust 的一些不涉及到所有权的特性以及如何巧妙地使用它们。对于 Rust 的所有权特性感兴趣的读者朋友,可以查看相关文档。

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

b0cb357d9882edd6e214b1e462a9b0f3.png

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

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

相关文章

第二十六章 案例TodoList 之实现Footer组件

本小节,我们来实现最后的Footer组件的功能,它的功能主要有: 记录已完成和全部的任务列表数量点击【复选框】可以实现全选和全不选点击【删除已完成】按钮,可以将选中的任务项删除掉 实现已完成和全部的任务列表数量 步骤1&#…

提交Spark应用程序

文章目录 一,提交语法格式二,spark-submit常用参数三,案例演示 - 提交Spark自带的圆周率计算程序(一)Standalone模式,采用client提交方式(二)Standalone模式,采用cluster…

PMP第六版_1~3章

回想起来,距离上次看pmp已经过去一年多, 等着等着,第七版都出来了.自己还是没下决心考.第六版也都忘干净了.哎. 越来越不记事了.一转眼2023年了.哎…时间过得真快. 做个记录,顺便勉励自己吧. PMP 项目管理 按照十大知识领域来写的 要有美式思维 题干量大(因中英语言区别) 越…

Linux网络服务远程访问及控制SSH(你明目张胆的偏爱就是救赎)

文章目录 一、SSH的介绍及其相关知识点1.简介2.SSH的优点3.SSH的密钥登录 二、SSH的运用1.存放ssh服务端的配置文件2.白名单&黑名单3.存放ssh客户端的配置文件 三、ssh密钥登录操作1.密码验证2.秘钥对验证3.ssh远程登录SCP 远程复制SFTP获取服务端的文件 四、秘钥对的创建操…

nodejs+vue 校友录校友捐赠系统

校友录的设计与实现该系统面对的是用户和管理员,对于用户来说其最大的作用是在校友录的设计与实现前台查看校友活动、校友风采、校友捐赠、班级录、新闻中心、,其次进行交流园地。对管理员而言则是对用户、活动类型、校友活动、校友风采、校友捐赠、班级…

PCIE内核注册详解

代码结构 在Linux内核中,PCIe驱动程序的注册和处理涉及到许多文件,其中一些主要的文件包括: drivers/pci/pci.h:这个文件定义了PCIe驱动程序结构体和相关的函数。驱动程序需要包含这个头文件才能使用PCIe相关的函数和结构体。 d…

【观察者设计模式详解】C/Java/JS/Go/Python/TS不同语言实现

简介 观察者模式(Observer Pattern)是一种行为型模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 观察者模式使用三个类Subject、Observer和Client。Subject…

ChatGPT热中的冷思考

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和…

taro之项目初始化模版

项目初始化模板 一直以来,在使用 Taro CLI 的 taro init 命令创建项目时,CLI 会提供若干内置模板给开发者选择。但是很多团队都有自己独特的业务场景,需要使用和维护的模板也不尽一致,因此 Taro 支持把项目模板打包成一个能力赋予…

思科模拟器 | 生成树协议STP、RSTP、HSRP配置

一、生成树协议STP 概念介绍: 生成树协议是一种网络协议,用于在交换机之间建立逻辑上的树形拓扑结构避免产生环路。为了完成这个功能,生成树协议需要进行些配置,包括根桥的选举、端口的状态切换等。 步骤明细: 使用思…

itop-3568 开发板系统编程学习笔记(21)PWM 应用编程

【北京迅为】嵌入式学习之Linux系统编程篇 https://www.bilibili.com/video/BV1zV411e7Cy/ 个人学习笔记 文章目录 在设备树打开 PWMsysfs 方式控制 PWMPWM 应用编程 在设备树打开 PWM RK3568 有 16 个 PWM 控制器,本文件将以 PWM0 为例进行实验,为什么…

如何在PowerShell中查找、添加、修改和删除环境变量:解决手动设置环境变量后报命令失效的问题

简介 PowerShell是Windows平台上的一种命令行工具,它可以方便地查找、添加、修改和删除环境变量。在本文中,LZ将介绍如何在PowerShell中进行这些操作。 查找环境变量 在PowerShell中查找环境变量非常简单。只需要使用Get-ChildItem命令,以…

LED驱动 中断

1、用字符设备驱动框架和平台设备驱动框架实现LED驱动 1.1 用字符设备驱动框架-----》led2 控制led2闪烁 1.应用层: 1 open(“/dev/haha0”) 2 while(1) ioctl(fd,LED_ON)&#xff…

java的社区养老服务系统 ssm空巢老人

创新点: 1、根据时间、类型统计用户下单记录,形成可视化图形(饼状图) 2、根据用户爱好推荐项目 包含模块:关于我们、联系我们、外链信息、资讯类型、服务资讯、服务类型、服务项目、案例类型、服务案例、讨论类型、讨论…

引入Tuning function design的自适应反步控制方法 上篇

引入Tuning function design的自适应反步控制方法 上篇 目录 引入Tuning function design的自适应反步控制方法 上篇尝试用推迟参数设计解决高阶不匹配系统的控制器设计问题问题描述控制器设计小结上一篇文章写了如何通过推迟参数设计的方法来解决不匹配条件下的系统反步控制设…

【原型设计模式详解】C/Java/JS/Go/Python/TS不同语言实现

简介 原型模式(Prototype Pattern)是一种创建型设计模式,使你能够复制已有对象,而无需使代码依赖它们所属的类,同时又能保证性能。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创…

IT项目管理之软件测试

1. 定义 软件测试是使用人工或者自动的手段来运行或者测定某个软件系统的过程,其目的在于检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别。 在软件投入使用前,要经过一系列的严格测试,才能保证交付质量。 2. QC & QA &a…

会声会影导入视频是黑色的 会声会影导入视频只有声音

会声会影是一款功能很成熟的视频编辑软件,其友好的界面设计能照顾到初学者的需求,同时配置的强大功能可满足进阶者的需要。不过由于或硬件或软件的原因,可能会出现会声会影导入视频是黑色的,会声会影导入视频只有声音的问题。本文…

Docker的实际应用

一、 数据持久化 我们什么情况下要做数据持久化呢? 一定是在做容器之前先预判好哪些文件是要永久存储的, 而不会跟着它容器的一个生命周期而消失。 比如说配置文件、 日志文件、 缓存文件或者应用数据等等。 数据初始化有三种类型。 第一种 volumes&…

浏览器缓存策略:强缓存和协商缓存

浏览器缓存:其实就是在本地使用的计算机中开辟一个内存区,同时也开辟一个硬盘区,作为数据传输的缓冲区,然后利用这个缓冲区来暂时保护用户以前访问的信息通常浏览器的缓存策略分为两种:强缓存和协商缓存,强…