Rust-所有权和移动语义

news2024/9/24 22:43:53

什么是所有权

拿C语言的代码来打个比方。我们可能会在堆上创建一个对象,然后使用一个指针来管理这个对象:

Foo *p =make_object("args");

接下来,我们可能需要使用这个对象:

use_object(p);

然而,这段代码之后,谁能猜得到,指针p指向的对象究竟发生了什么?它是否被修改过了?它还存在吗,是否已经被释放?是否有另外一个指针现在也同时指向这个对象?我们还能继续读取、修改或者释放这个对象吗?实际上,除了去了解use_object的内部实现之外,我们没办法回答以上问题。

对此,C++进行了一个改进,即通过“智能指针”来描述“所有权”(Ownership)概念。

这在一定程度上减少了内存使用bug,实现了“半自动化”的内存管理。

而Rust在此基础上更进一步,将“所有权”的理念直接融入到了语言之中。

“所有权”代表着以下意义:

  • 每个值在Rust中都有一个变量来管理它,这个变量就是这个值、这块内存的所有者;
  • 每个值在一个时间点上只有一个管理者;
  • 当变量所在的作用域结束的时候,变量以及它代表的值将会被销毁。

拿前面已经讲过的字符串String类型来举例:

在这里插入图片描述
当我们声明一个变量s,并用String类型对它进行初始化的时候,这个变量s就成了这个字符串的“所有者”。如果我们希望修改这个变量,可以使用mut修饰s,然后调用String类型的成员方法来实现。

当main函数结束的时候,s将会被析构,它管理的内存(不论是堆上的,还是栈上的)则会被释放。

我们一般把变量从出生到死亡的整个阶段,叫作一个变量的“生命周期”。

比如这个例子中的局部变量s,它的生命周期就是从let语句开始,到main函数结束。

在上述示例的基础上,若做一点修改:

在这里插入图片描述
这里出现了编译错误。编译器显示,在let s1 =s;语句中,原本由s拥有的字符串已经转移给了s1这个变量。所以,后面继续使用s是不对的。

也就是前面所说的每个值只有一个所有者。变量s的生命周期从声明开始,到move给s1就结束了。

变量s1的生命周期则是从它声明开始,到函数结束。

而字符串本身,由String::from函数创建出来,到函数结束的时候就会销毁。

中间所有权的转换,并不会将这个字符串本身重新销毁再创建。

在任意时刻,这个字符串只有一个所有者,要么是s,要么是s1。

在用变量s初始化s1的时候,并不会造成s的生命周期结束。

这里只会调用string类型的复制构造函数复制出一个新的字符串,于是在后面s1和s都是合法变量。

在Rust中,我们要模拟这一行为,需要手动调用clone()方法来完成:

在这里插入图片描述

在Rust里面,不可以做“赋值运算符重载”,若需要“深复制”,必须手工调用clone方法。

这个clone方法来自于std::clone::Clone这个trait。clone方法里面的行为是可以自定义的。

移动语义

一个变量可以把它拥有的值转移给另外一个变量,称为“所有权转移”。

赋值语句、函数调用、函数返回等,都有可能导致所有权转移。

在这里插入图片描述
所有权转移的步骤分解如下。
在这里插入图片描述

  1. main函数调用create函数。
  2. 在调用create函数的时候创建了字符串,在栈上和堆上都分配有内存。局部变量s是这些内存的所有者。
  3. create函数返回的时候,需要将局部变量s移动到函数外面,这个过程就是简单地按字节复制memcpy。
  4. 同理,在调用consume函数的时候,需要将main函数中的局部变量转移到consume函数,这个过程也是简单地按字节复制memcpy。
  5. 当consume函数结束的时候,它并没有把内部的局部变量再转移出来,这种情况下,consume内部局部变量的生命周期就该结束了。这个局部变量s生命周期结束的时候,会自动释放它所拥有的内存,因此字符串也就被释放了。

Rust中所有权转移的重要特点是,它是所有类型的默认语义。

这里再重复一遍,请大家牢牢记住,Rust中的变量绑定操作,默认是move语义,执行了新的变量绑定后,原来的变量就不能再被使用!一定要记住!

Rust的这一规定非常有利于编译器静态检查。

与之相对的,C++的做法就不一样了,它允许赋值构造函数、赋值运算符重载,因此在出现“构造”或者“赋值”操作的时候,有可能表达的是完全不同的含义,这取决于程序员如何实现重载。C++的这个设计具有巨大的灵活性,但是不恰当的实现也可能造成内存不安全。

而Rust的这一设计大幅降低了语言的复杂度,“移动语义”不可能执行用户的自定义代码,没有任何内存安全风险,而且满足异常安全。

在C++里面,std::vectorv1 =v2;是复制语义,而Rust里面的let v1:Vec=v2 ;是移动语义。如果要在Rust里面实现复制语义,需要显式写出函数调用let v1:Vec=v2.clone();。如果我们在C++中实现移动语义,则需要户自定义实现移动构造函数及移动赋值运算符。

对于“移动语义”,最后还需要强调的一点是,“语义”不代表最终的执行效率。

“语义”只是规定了什么样的代码是编译器可以接受的,以及它执行后的效果可以用怎样的思维模型去理解。

编译器有权在不改变语义的情况下做任何有利于执行效率的优化。

语义和优化是两个阶段的事情。我们可以把移动语义想象成执行了一个memcpy,但真实的汇编代码未必如此。比如:

在这里插入图片描述
完全可能被优化为类似如下的效果:
在这里插入图片描述
编译器可以提前在当前调用栈中把大对象的空间分配好,然后把这个对象的指针传递给子函数,由子函数执行这个变量的初始化。

这样就避免了大对象的复制工作,参数传递只是一个指针而已。

这么做是完全满足移动语义要求的,而且编译器还有权利做更多类似的优化。

复制语义

默认的move语义是Rust的一个重要设计,但是任何时候需要复制都去调用clone函数会显得非常烦琐。

对于一些简单类型,比如整数、bool,让它们在赋值的时候默认采用复制操作会让语言更简单。

比如下面这个程序就可以正常编译通过:

在这里插入图片描述

编译器并没有阻止v1被使用,这是为什么呢?

因为在Rust中有一部分“特殊照顾”的类型,其变量绑定操作是copy语义。

所谓的copy语义,是指在执行变量绑定操作的时候,v2是对v1所属数据的一份复制。

v1所管理的这块内存依然存在,并未失效,而v2是新开辟了一块内存,它的内容是从v1管理的内存中复制而来的。和手动调用clone方法效果一样,let v2 =v1;等效于let v2 =v1.clone();。

使用文件系统来打比方。

copy语义就像“复制、粘贴”操作。操作完成后,原来的数据依然存在,而新的数据是原来数据的复制品。

move语义就像“剪切、粘贴”操作。

操作完成后,原来的数据就不存在了,被移动到了新的地方。

这两个操作本身是一样的,都是简单的内存复制,区别在于复制完以后,原先那个变量的生命周期是否结束。

Rust中,在普通变量绑定、函数传参、模式匹配等场景下,凡是实现了std::marker::Copy trait的类型,都会执行copy语义。

基本类型,比如数字、字符、bool等,都实现了Copy trait,因此具备copy语义。

对于自定义类型,默认是没有实现Copy trait的,但是我们可以手动添上。示例如下:

在这里插入图片描述

编译通过。现在Foo类型也拥有了复制语义。在执行变量绑定、函数参数传递的时候,原来的变量不会失效,而是会新开辟一块内存,将原来的数据复制过来。

绝大部分情况下,实现Copy trait和Clone trait是一个非常机械化的、重复性的工作,clone方法的函数体要对每个成员调用一下clone方法。Rust提供了一个编译器扩展derive attribute,来帮我们写这些代码,其使用方式为#[derive(Copy,Clone)]。

只要一个类型的所有成员都具有Clone trait,我们就可以使用这种方法来让编译器帮我们实现Clone trait了。
在这里插入图片描述

Box类型

Box类型是Rust中一种常用的指针类型。它代表“拥有所有权的指针”,类似于C++里面的unique_ptr(严格来说,unique_ptr更像Option<Box>)。

在这里插入图片描述Box类型永远执行的是move语义,不能是copy语义。

原因大家想想就可以明白,Rust中的copy语义就是浅复制。对于Box这样的类型而言,浅复制必然会造成二次释放问题。

对于Rust里面的所有变量,在使用前一定要合理初始化,否则会出现编译错误。

对于Box/&T /&mut T这样的类型,合理初始化意味着它一定指向了某个具体的对象,不可能是空。

如果用户确实需要“可能为空的”指针,必须使用类型option<Box>。

Rust里面还有一个保留关键字box(注意是小写)。它可以用于把变量“装箱”到堆上。目前这个语法依然是unstable状态,需要打开feature gate才能使用,示例如下:

在这里插入图片描述

Copy的实现条件

并不是所有的类型都可以实现Copy trait。Rust规定,对于自定义类型,只有所有成员都实现了Copy trait,这个类型才有资格实现Copy trait。

常见的数字类型、bool类型、共享借用指针&,都是具有Copy属性的类型。而Box、Vec、可写借用指针&mut等类型都是不具备Copy属性的类型。

对于数组类型,如果它内部的元素类型是Copy,那么这个数组也是Copy类型。对于元组tuple类型,如果它的每一个元素都是Copy类型,那么这个tuple也是Copy类型。

struct和enum类型不会自动实现Copy trait。只有当struct和enum内部的每个元素都是Copy类型时,编译器才允许我们针对此类型实现Copy trait。比如下面这个类型,虽然它的成员是Copy类型,但它本身不是Copy类型:

在这里插入图片描述

自动derive

绝大多数情况下,实现Copy Clone这样的trait都是一个重复而无聊的工作。因此,Rust提供了一个attribute,让我们可以利用编译器自动生成这部分代码。示例如下:
在这里插入图片描述
这里的derive会让编译器帮我们自动生成impl Copy和impl Clone这样的代码。自动生成的clone方法,会依次调用每个成员的clone方法。

通过derive方式自动实现Copy和手工实现Copy有微小的区别。

当类型具有泛型参数的时候,比如struct MyStruct{},通过derive自动生成的代码会自动添加一个T:Copy的约束。

目前,只有一部分固定的特殊trait可以通过derive来自动实现。

将来Rust会允许自定义的derive行为,让我们自己的trait也可以通过derive的方式自动实现。

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

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

相关文章

初识OpenCV

首先你得保证你的虚拟机Ubuntu能上网 可看 http://t.csdnimg.cn/bZs6c 打开终端输入 sudo apt-get install libopencv-dev 回车 输入密码 回车 遇到Y/N 回车 OpenCV在线文档 opencv 文档链接 点zip可以下载&#xff0c;点前面的直接在线浏览&#xff0c;但是很慢 https…

AI嵌入式K210项目(3)-GPIO控制

文章目录 前言一、背景知识二、背景知识二、开始你的表演代码实现 总结 前言 前面介绍了开发板和环境搭建的基本情况&#xff0c;接下来我们开始学习使用C进行裸板开发&#xff0c;本节课先来学习下K210最基础的功能&#xff0c;引脚映射和点灯。 在开始具体学习之前&#xff…

跟着cherno手搓游戏引擎【4】窗口抽象、GLFW配置、窗口事件

引入GLFW&#xff1a; 在vendor里创建GLFW文件夹&#xff1a; 在github上下载&#xff0c;把包下载到GLFW包下。 GitHub - TheCherno/glfw: A multi-platform library for OpenGL, OpenGL ES, Vulkan, window and input修改SRC/premake5.lua的配置&#xff1a;12、13、15、36…

多云架构下的点击流数据分析

在出海的大趋势下&#xff0c;需要对点击流数据进行分析&#xff0c;以便更快的确定客户。作为多家云厂商的合作伙伴&#xff0c;九河云将提供点击流数据分析的改良方案。 对于这个需求可以借助aws的受众细分和定位解决方案&#xff0c;您可以应用基于云的分析和机器学习来减少…

Seaborn——可视化的具体API应用

一、Seaborn概述 Seaborn 是基于 matplotlib的图形可视化 python包。提供了一种高度交互式界面&#xff0c;便于用户能够做出各种有吸引力的统计图表。 Seaborn在 matplotlib的基础上进行了更高级的API封装&#xff0c;从而使得作图更加容易&#xff0c;在大多数情况下使用seab…

高可用架构去中心化重要?

1 背景 在互联网高可用架构设计中&#xff0c;应该避免将所有的控制权都集中到一个中心服务&#xff0c;即便这个中心服务是多副本模式。 对某个中心服务&#xff08;组件&#xff09;的过渡强依赖&#xff0c;那等同于把命脉掌握在依赖方手里&#xff0c;依赖方的任何问题都可…

kafka学习笔记-- 文件清理策略与高效读写数据

本文内容来自尚硅谷B站公开教学视频&#xff0c;仅做个人总结、学习、复习使用&#xff0c;任何对此文章的引用&#xff0c;应当说明源出处为尚硅谷&#xff0c;不得用于商业用途。 如有侵权、联系速删 视频教程链接&#xff1a;【尚硅谷】Kafka3.x教程&#xff08;从入门到调优…

机器学习学习笔记(吴恩达)(第三课第一周)(无监督算法,K-means、异常检测)

欢迎 聚类算法&#xff1a; 无监督学习&#xff1a;聚类、异常检测 推荐算法&#xff1a; 强化学习&#xff1a; 聚类&#xff08;Clustering&#xff09; 聚类算法&#xff1a;查看大量数据点并自动找到彼此相关或相似的数据点。是一种无监督学习算法 聚类与二院监督…

如何使用服务器?

文章目录 如何使用服务器&#xff1f;一、工具二、第一种方法三、第二种方法四、实例 个人经验 如何使用服务器&#xff1f; 本文详细介绍了如何利用服务器跑模型&#xff0c;具体流程如下&#xff1a; 一、工具 ToDeskPyCharm Professional移动硬盘JetBrains GatewayGit 二…

微信小程序------WXML模板语法之条件渲染和列表渲染

目录 前言 一、条件渲染 1.wx:if 2. 结合 使用 wx:if 3. hidden 4. wx:if 与 hidden 的对比 二、列表渲染 1. wx:for 2. 手动指定索引和当前项的变量名* 3. wx:key 的使用 前言 上一期我们讲解wxml模版语法中的数据绑定和事件绑定&#xff08;上一期链接&#xff1a;…

溯源阿里巴巴的中台架构

明朝可以说是中国封建王朝中最后一个由汉人统治的王朝&#xff0c;就算是最后清王朝也是不断的学习汉人的治国方略&#xff0c;但是学习最多的当然是明朝。 其实阿里巴巴的中台战略其实和明朝的历史还是蛮像的&#xff0c;这里小编就和大家好好的探讨一下。 今天先来从明朝的…

Vue入门七(Vuex的使用|Vue-router|LocalStorage与SessionStorage和cookie的使用)

文章目录 一、Vuex1&#xff09;理解vuex2&#xff09;优点3&#xff09;何时使用&#xff1f;4&#xff09;使用步骤① 安装vuex② 注册vuex③ 引用vuex④ 创建仓库Store五个模块介绍 5&#xff09;基本使用 二、Vue-router三、LocalStorage与SessionStorage、cookie的使用 一…

漏洞复现-金和OA jc6/servlet/Upload接口任意文件上传漏洞(附漏洞检测脚本)

免责声明 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…

python统计分析——生成正态分布随机数

参考资料&#xff1a;用python动手学统计学&#xff0c;帮助文档 方法1&#xff1a;scipy.stats.norm.rvs() from scipy import stats stats.norm.rvs(loc4,scale0.8,size10) 参数介绍如下&#xff1a; loc&#xff1a;表示正态分布的均值&#xff0c;默认为0 scale&#…

WinSCP传文件到Ubuntu提示Permission denied

使用WinSCP传文件到一台Ubuntu服务器时&#xff0c;提示Permission denied。 客户端&#xff1a;Windows 10 服务器&#xff1a;hyper-V虚拟机 Ubuntu 20.04 WinSCP版本&#xff1a;WinSCP 6.1 文章目录 WinSCP工具介绍WinSCP开源免费WinSCP优点 Permission denied 解决方法scp…

机器学习——主成成分分析PCA

如上图所示&#xff0c;一共有4个属性&#xff0c;身高&#xff08; m为单位&#xff09;&#xff0c;身高&#xff08;cm&#xff09;,时速&#xff08;每小时公里&#xff09;&#xff0c;时速&#xff08;每小时里&#xff09;&#xff0c;身高的两个属性是相关的&#xff0…

bee工具的使用及创建第一个项目

前提文章&#xff1a;beego的安装及配置参数说明-CSDN博客 提示&#xff1a;beego框架下项目需要再GOPATH/src下进行开发&#xff0c;我的GOPATH是C:\Users\leell\go web项目创建 通过 bee new 创建web项目 C:\Users\leell\go\src>bee new beego-web 2024/01/15 21:40:0…

文心一言 vs. ChatGPT:哪个更胜一筹?

文心一言 vs. ChatGPT&#xff1a;从简洁美到深度思考的文本生成之旅 近年来&#xff0c;文本生成工具的崛起使得人们在表达和沟通方面拥有了更多的选择。在这个领域中&#xff0c;文心一言和ChatGPT作为两个备受瞩目的工具&#xff0c;各自以独特的优势展现在用户面前。本文将…

深度系统QT 环境搭建

1.QT安装 不折腾最新版直接去商店搜索QT安装。 2.修改su密码&#xff0c;安装需要权限 打开一个终端&#xff0c;然后输入下面的命令&#xff1a;按照提示输入密码按回车就行。 sudo passwd 回车后会出现让你输入现在这个账户的密码&#xff1a; 3.编译环境安装。 安…

[链路层] 点对点协议 PPP

目录 1、PPP协议的特点 2、PPP协议的组成和帧格式 3、PPP协议的工作状态 目前使用得最广泛的数据链路层协议是点对点协议PPP(Point-to-Point Protocol)。 1、PPP协议的特点 我们知道&#xff0c;互联网用户通常都要连接到某个 ISP 才能接入到互联网。PPP 协议就是用户计算机…