rust学习五、认识所有权

news/2025/1/12 18:16:43/文章来源:https://www.cnblogs.com/lzfhope/p/18514049

在<<The rust programming language>>的中译版<<rust权威指南>>中,作者用了30页的篇幅来阐述这个问题。

如作者所言,所有权是学习rust语言的基础,不掌握这个,无需继续往下,所以,这是初学rust就必须会的。

 

正是所有权概念和相关工具的引入,Rust才能够在没有垃圾回收机制的前提下保障内存安全。

一、变量的存储方式和赋值方式

要进入rust所有权范围讨论问题,那么必须先理解RUST的变量的存储方式赋值方式

rust出于各种目的,规定变量可以存放在栈和堆上:

  1. 栈-存放哪些编译时期就知道大小的。通常存储那些简单的数据类型,例如整数、浮点、布尔、字符、成员类型都是整数、浮点、布尔、字符之一的元组
    • 注意这是一个FILO(先进后出,或者是后进先出)类型的,好似堆碟子,反而最上面的最先用。
  2. 堆-存放那些编译时期无法知道其大小的。例如String(字符串)

栈变量转赋

对于栈变量而言,把a赋值给b,仅仅是一种在栈中复制数据的过程-快速且成本小。

let a=10;
let b=a;

 上面这个代码中,就是把10复制一份给b。

 

堆变量转赋

但是堆不同,这个就是所有权的问题的来源,见后文。

堆变量和java的类变量是相识的存储方式,都是用一个地址指向实际的存储区域,如下图(来自书本):

 

所有权问题就是堆变量问题。

二、所有权规则是如何产生的?所有权规则是什么?

2.1、规则怎么来?

要理解所有权,就需要从rust的设计目的谈起,否则难于理解有些概念。

按照rust官方的说法,rust要保证内存安全(一定会释放掉),同时还需要保证高效(不能用java 那样的gc管理器),同时还不能太繁琐(像C++那样手动释放)

所以,他们想到了一个主意:一个变量应该用完就自动释放(通常是立刻释放)

通过设这种设定,那么就解决了一些问题:

  • 不会内存泄漏
  • 不需要借助额外的垃圾收集器,增大了用于系统编程的可行性

但这会引发一个问题,怎么知道需要被移除的变量没人用了?

像java那样使用引用计数器之类的?但是现在不搞那一套,就需要定个规矩:一个变量一定要有所有者;而且任意时刻只能有一个所有者

和前面提到的结合起来,就是rust著名的所有权规则:

  1. 一个值(不是变量)一定要有所有者
  2. 而且任意时刻,一个值只能有一个所有者
  3. 所有者离开作用域后,持有的值会被立刻释放

因为任意时候一个值只有一个所有者,所以一旦所有者离开返回,就可以简单“高效”地执行释放操作,不用担心还被谁用了。

规则就是为了达到这个目的。

 

这个规则有一个瑕疵:按照rust官方的说法,有个drop程序会立刻做这个事情,那么这某种程度上会降低性能。

但是现代高级语言都没有解决这个问题,哪怕C++之类的,所以这个就不算是一个问题了。大家都装糊涂吧,也许某天cpu和操作系统变化了之后,可以解决这个问题。

所以,这应该是一个当前情况下,权衡后的可接受方案。

 

2.2、规则怎么得到保证?

规则定了,那么如何保证这些规则可以得到遵守?

1.明确作用域的

如果是有明确作用域的,例如{}或者函数,那么很好理解:

fn test(){let s:String=String::from("种瓜得豆");println!("{}",s);
}

绝大部分语言都是这么规定,所以能够立刻理解!离开函数或者明确的作用域,就应该要释放掉。

 

2.在一个作用域之内

参考书中的经典例子:

let s1=String::from("锦绣中华");
let s2=s1;
println!("{}",s1); 

 通不过编译!

let s1=String::from("锦绣中华");|         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait12 |     let s2=s1;|            -- value moved here13 |     println!("{}",s1);|                   ^^ value borrowed here after move

第一次接触这个,我有一点震惊,是因为两个没有想到:

1.怎么会不行?

2.编译器就能够解决这个问题,而不是在运行时发生。编译器够厉害的。

 

现在再回过头看下这个编译错误提示,提示大意是这样的:

由于s1是String类型,所以发生的移动情况。String并没有实现Copy trait(类型特性)(意思就是不能通过复制完成值的转赋)

当指定s2=s1的时候,s1的值已经转给了s2(s1已经没有值了)

当再次使用s1的时候,这就是一种典型的错误行为:值被移动后企图借用

 

通过这个提示我们可以看到:在一个作用域之内,如果一个变量转赋给另外一个变量的时候,会发生一件事情值会从一个变量转到另外一个变量上

为什么要这么做? 因为只有这样才能保证前面提到的第二个原则:任意时刻只能有一个变量拥有某个值

 

回头再看编译提示,我们还知道一点:类似s1这样的变量只所以会发生这个值转移情况,是因为s1是String,默认没有实现Copy类型特性。

反过来说,如果String实现了Copy类型特性,那么就不会发生这种编译错误。

 

2.3、规则带来的其它问题

虽然三个规则保证了目的,但是会带来不少问题(麻烦),比较典型的问题就是:

一个变量的值被移走后,那么再次使用原来的变量就变得麻烦了。 因为需要再次移动值。如果这样写代码,太冗余啰嗦,谁也受不了!

以下是一个奇怪的例子。

fn main(){let mut  address:String=String::from("福建福州");println!("{}",address);print_str(address);//再用address,那么久会报错,如果要不报错,则必须//插入诸如 address=xxx之类语句,把值移回来,或者重新赋值println!("新地址:{}",address) ;  //报错 
}fn  print_str(s:String){println!("{}",s);
}

 

在上例中,如果为了避免println!("新地址:{}",address)报错,必须需要在这句话之前做一些事情。这样无疑太麻烦了。

如果按照上面这种方法,那么rust就没有存在的意义,因为一门语言不但要考虑性能、安全等,也需要考虑工程效果:不能让工程师烦

为了解决这个问题,rust给出了三个解决方案

  1. 不可变引用-通过某种方式标记这是一种借用,值所有权没有转移。借用完成后会自动归还。当前借用不能修改值
  2. 可变引用   -通过某种方式标记这是一种借用,值所有权没有转移,借用完成后会自动归还。这个期间,其它变量不能同时用这个值。但是当前借用能修改这个值
  3. 切片引用-只借用了部分(也可以是全部),可以细分为不可变切片可变切片

通过这3个方案,编译器已经帮工程师默默地完成了借用和自动归还的操作,工程师不需要再操心了。

 

借用是如何实现的,借用原书的2幅图

图_引用

图_切片引用

 

原书还列出两个引用原则:

  1. 在任何一段给定的时间内,你要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用                   
  2. 引用总是有效的

 

后文演示这几个例子

2.3.1不可变引用

fn main() {let name = String::from("岳飞");//引用,被引用多少次都无所谓print_me(&name);print_me(&name);let n1 = &name;let n2 = &name;println!("n1={},n2={}", n1, n2);
}

 

书写格式: &s

name可以同时被不可变借用多次。

这意味着,并发情况下可以共用一个值。

2.3.2可变引用

书写格式: &mut s

fn mut_borrow() {let mut s = String::from("飞翔");let s1 = &mut s;println!("{}", s1);s1.push_str("在太空!");println!("{}", s1);let s2 = &mut s;println!("{}", s2);//println!("{}",s1); //在已经借给s2的情况下,再使用s1会报错 -- first borrow later used herelet s3 = &mut s;s3.push_str("星光灿烂");println!("{}", s3);let s4 = &mut s;let s5 = &mut s;let s6 = &s;
}

 

由于只能同时有一个可变引用,所以并发是一个问题。

2.3.3切片引用

书写格式:

&s[..]   -- 等同于s
&s[n..m] -- 取从n到m-1的部分,其中n>=0
&s[..n]  -- 取0到n-1的部分
&s[n..]  -- 取从n及其之后的所有

特别地,字符串切片类型写成 &str。

同时定义多个不可变切片,并可同时用

fn show_string_slice(){let name=String::from("ABCDEF GHIJKLMN");let s1=&name[0..4];let s2=&name[5..10];println!("s1={},s2={}",s1,s2);
}

 

其它切片演示:

可变的数组切片

fn test_mut_slice(){let mut numbers = [1, 2, 3, 4, 5];  let slice: &mut [i32] = &mut numbers[0..1]; // 获取整个数组的可变切片  slice[0] = 10; // 修改切片中的第一个元素,这也会修改原始数组  println!("{:?}", numbers); // 输出: [10, 2, 3, 4, 5]
}

 

三、小节

  1. rust的值的所有权规则是一个非常独特的内容,其它语言暂时没有这样的情况
  2. rust的所有权有一点难于理解,但务必理解,因为这是继续的基础,此关不通,不要考虑后面的学习内容
  3. rust的所有权,可以算是一种相对高效的主动垃圾回收机制,是一种可以接受的妥协
  4. rust提供了多种引用方式,使得同时共享一个值变得可能,例如引用,切片引用
  5. rust的编译器默默地干了很多的事情

 

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

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

相关文章

给予爱快路由器使用阿里云DDNS远程访问家庭网络

一、阿里云域名准备1、首先需要购买一个阿里云域名,选简短好记性价比高的即可,如果域名只是用来解析路由器IP地址,不做服务器类应用,则不需要备案。 2、想做动态域名解析,光有域名还不行,还需要在阿里云控制台建立一个访问授权,路由器才能动态同步远营商的IP地址到DNS服…

KingbaseES V8R6集群备份恢复案例之---主库single-pro备份恢复

KingbaseES、repmgr、sys_rman案例说明: KingbaseES V8R6集群物理备份支持single-pro方式,本案例在集群执行single-pro方式备份并多次切换集群后,对集群执行了恢复测试,文档记录了恢复的详细过程。 适用版本: KingbaseES V8R6 集群架构:ID | Name | Role | Status …

号码变换配置对接运营商IMS

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 fs直接对接运营商,调试过程中的号码变换规则比较容易出问题。 本文档记录一个较为通用的对接IMS配置方案。 环境 CentOS 7.9 freeswitch 1.10.7 模块配置 号码变换主要使用mod_translate模块和dialplan拨号计划实现。 确…

Idea上Git仓库不见了是什么原因

在使用IntelliJ IDEA进行项目开发时,Git仓库突然消失是开发者常遇到的问题。该问题可能由多个因素引起,包括:1.环境配置问题;2.软件或插件更新;3.目录结构变更;4.用户权限问题;5.其他软件干扰。理解这些因素并采取相应的解决措施,不仅能快速恢复Git仓库,还能避免类似问…

【算法学习】扫描线

这篇题解写的难以言喻,可能只有我能看的懂! 前言 虽然我觉得这个算法目前不太可能会考,但是我觉得挺有意思的,而且学个算法也挺好,我是为自己学的!!! 定义 扫描线可以求二维图形的面积,也可以求周长等多种用途…… P5490 【模板】扫描线 & 矩形面积并 这就是扫描线…

leetcode 740 删除并获得点数

740 删除并获得点数 题意 给你一个整数数组 nums ,你可以对它进行一些操作。 每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。 开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。…

Navicat 17下载与安装

1、安装包 Navicat 17: 链接:https://pan.quark.cn/s/c75e892c4705 提取码:YvyF Navicat 16: 链接:https://pan.quark.cn/s/63c07b20ea7b提取码:B9ij 2、安装教程(这里以安装Navicat 17 为例) 1) 如之前已安装的需卸载当前Navicat,如未安装,直接双击无限试用…

gitlab怎么保护分支

​GitLab作为一个流行的版本控制工具其中“分支保护”是一个关键功能,用以防止开发过程中的不当操作对代码造成不可逆的影响。本文将指导你如何在GitLab中保护分支:1.理解保护分支的重要性;2.学会使用GitLab的界面进行分支的保护操作;3.了解与合并请求的关联使用;4.探讨在…

[编程笔记] 搞人心态的代码含毒事件 “svn无法成功完成操作因为文件包含病毒或潜在的垃圾软件”

svn无法成功完成操作因为文件包含病毒或潜在的垃圾软件,Windows Defender误判?今天突然冒出来的问题,烦死了!     svn拉取代码报毒了,不用想,基本就是下面几个可能性:1、某人提交的代码有毒2、电脑上的第三方杀毒软件引发3、Windows Defender误判报毒的代码是一个dl…

AI作文批阅,AI素材管理……璞华集团携多款明星产品亮相智能社会治理论坛

2024年10月25日,金秋十月的璀璨时节,备受瞩目的第二届智能社会治理论坛暨中国光谷人工智能艺术大会在中国光谷盛大启幕。此次论坛汇聚了人工智能领域的顶尖智慧,共同探讨人工智能技术的最新突破与智能社会治理模式的创新路径,同时强调了科技与文化融合的无限可能。璞华集团…

有Redis为什么还要本地缓存?谈谈你对本地缓存的理解?

本地缓存是将数据存储在应用程序所在的本地内存中的缓存方式。既然,已经有了 Redis 可以实现分布式缓存了,为什么还需要本地缓存呢?接下来,我们一起来看。 为什么需要本地缓存? 尽管已经有 Redis 缓存了,但本地缓存也是非常有必要的,因为它有以下优点:速度优势:本地缓…

Adobe After Effects各版本安装包下载与安装

1、安装包我用夸克网盘分享了 After Effects 2024: 链接:https://pan.quark.cn/s/fac88adbac44 提取码:9ZMW After Effects 2023: 链接:https://pan.quark.cn/s/d41a0a447b93 提取码:4pwM After Effects 2022: 链接:https://pan.quark.cn/s/0070a59da58d 提取码:Eij1 Af…