Rust之路(4)——所有权
【未经书面同意,严禁转载】 -- 2020-10-14 --
所有权是Rust的重中之重(这口气咋像高中数学老师 WTF......)。
所有权是指的对内存实际存储的数据的访问权(包括读取和修改),在大多数语言中,一个数据赋值给一个变量,这个变量就拥有了数据的访问权。然后可以定义很多引用指向这个变量和这段数据,所有的引用都可以修改它。但在Rust中刷新了这种观念:读数据可以共享,但写数据必须是独占的!这样就能保证数据安全性,以及并发过程中的数据竞争。Rust用所有权来实现这一切。
所有权和另外两个概念借用和生命周期是相关的,可以说是一体的。借用其实就是前面讲的引用干的事情。大多数Rust书籍——包括官网的《The Book》——都是按章节一 一介绍,总感觉不那么立体,理解起来也慢。
- 引用(借用):borrowing
- 所有权:ownership
- 生命周期:lifetimes
搞定三者应该是一揽子工程。
经闭关苦思冥想,我总结了一个形象的比喻,和读者分享。
每个人都有一张身份证,来记录个人的唯一信息身份证号,以及姓名:张三、性别:男 等个人信息,你可以以这个身份拥有房产、汽车、存款等财物(相当于内存中的实际数据),并可以修改房产的布局、启动汽车、存取款。这些财物属于这个身份的,这个身份拥有这些财物,这个身份对财物拥有所有权。这个时候,任何其他人无权进你的房间、汽车、私自查你的账户。
如果李四向张三打了招呼:我想去你房子里看看,张三允许了李四去参观房子、查看账户,但此时未经张三的书面允许还不能去搬动或迁移财物。借用者只能使用,不能改变,这是只读引用,也叫共享引用,因为可以有多人同时使用,这时候王五、赵六都可以同时去查看张三的财物信息。
然而如果有胡七取得了张三的身份证,套用了他的身份,就可以通过这个身份转移财产、修改财物的属性。但Rust为了避免多人同时修改数据,除了胡七能修改财物,这时候连张三也是个木头人了,自己都无权去改变自己的财物。而胡七知道自己随时要修改财物,也不允许任何其他人来查看,以防看到的信息随后就被改变而失效。这种情况一直延续到胡七放弃这个身份,财物才返还给张三。
另外,张三可以把自己的财物转让给别人周八,这时候张三的身份下就没有财物了。如果再想通过张三获取财物信息,就会出错。注意和以上借用情况的区别,前面是财物的所有权一直归张三所有,而此处,是所有权已经到了周八手中。
------以上比喻仅限于此处所述情节,至于现实中的诸多无关因素请忽略------
这就所有权的概念,内存上的数据就像财物,只能有一个人真正拥有(现实生活中就算两个人拥有一套房,也得按比例分配),财物可以转让,共享信息,也可以借出去。对借用者来说,可以借来一个观看权,这个权并不是所有权,也可以借来所有权,这时候财物是其他人不能访问的。
此外,再加上时间这个维度!
张三从出生到挂了,是他的一个生命周期。出生后的某个时间点拥有了财物,相当于给张三的身份做了值初始化,有了实际的数据。如果有人借用他的身份,无论是只读引用(李四)还是可变引用(胡七),都必须在张三有了数据之后才能借用。张三在挂的时候,他名下的所有的财物都要销毁,所以在张三消失之前,李四胡七等人要先把借用销账(要么改而指向其他活着的人,要么自杀)。
张三的财物被周八拥有后,张三就恢复了两袖清风的状态,相当于变量又变为未初始化状态。Rust中,未初始化的变量只能赋新值,是不能直接使用的。
一、
let mut Zhang_San = "1套房产".to_string();
//创建变量Zhang_San,并用字符串初始化,这个字符串的所有者是Zhang_San
//------------------------------------------------------------
二、
let mut Zhang_San = "1套房产".to_string();
Zhang_San += "1台车";
//Zhang_San声明的时候用了mut关键字,可以修改这个字符串
//------------------------------------------------------------
三、
let mut Zhang_San = "1套房产".to_string();
Zhang_San += "1台车";
let Li_Si = &Zhang_San;
//这时候,Li_Si用只读方式借用了Zhang_San,Zhang_San和Li_Si都可以访问字
符串,但都不能更改,数据的所有权还是归Zhang_San
//------------------------------------------------------------
四、
let mut Zhang_San = "1套房产".to_string();
Zhang_San += "1台车";
let Li_Si = &Zhang_San;
let Wang_Wu = &mut Zhang_San;
//创建了可变引用后,所有其他的引用全部失效,如果后面代码再使用Li_Si,程序会
出错。此时Zhang_San拥有所有权,但是不能使用,只能使用Wang_Wu访问和修改
//------------------------------------------------------------
五、
let mut Zhang_San = "1套房产".to_string();
Zhang_San += "1台车";
let Li_Si = &Zhang_San;
let Wang_Wu = &mut Zhang_San;
let Hu_Qi = Zhang_San;
//Hu_Qi把Zhang_San的所有权拿走,现在Zhang_San以及所有Zhang_San的引用全
部不能再用。这叫做所有权的移动(Move)。
需要注意的是:如果Zhang_San的类型不是字符串,而是整型、布尔等基本类型(基本类型的含义依然是数据类型上篇开头的含义),第五段代码的运行就会不完全一样。Hu_Qi通过赋值运算符(=)取得的,不是Zhang_San的所有权和数据,而是一份拷贝,Hu_Qi拥有这份拷贝数据的所有权。这是因为基本类型都实现了Copy trait,在赋值的时候,会隐式调用拷贝行为,源数据继续存在。
由于所有权的概念,把向量中某个元素赋值给其他变量是不行的。
let mut v = vec![100, 101, 102, 103];
let third = v[2]; //错误!
因为向量是一个整体,如果某个元素的所有权被移走,向量就像被打掉了一个门牙。。。漏风了。不雅观也不健康。因此,这种情况,应该用引用(借用):let third = &v[2],如果需要改动这个值,则使用可变引用:let third = &mut v[2]。
需要注意的还有在循环语句中,要禁止变量的移动,而是用引用替代:
let x = vec![10, 20, 30];
while f() {
g(x); // 错误!在循环第一次后,x就失去所有权变为未初始化变量了
} // 然而下面这种方式就可以编译,每次失去所有权后,重新给它一个值,又初始化了
let mut x = vec![10, 20, 30];
while f() {
g(x); // 移走x
x = h(); // x重新赋值,下次循环又可以消费x了
}
所有权树
每个变量拥有一个值,如果这值是复杂类型,它又可以拥有其他值,例如:
let v = vec![1, 2, 3];
let arr = ['A', 'B'];
let t = (v, arr);
t 拥有一个元组的所有权,这个元组手握一个向量和一个数组的所有权,而其中的向量有三个整型的所有权,数组由两个字符类型的所有权。
所有者 t 和拥有的值形成了树。在所有权树的最终根是一个变量t;当这个变量超出范围时,整个树也跟着它销毁。Rust的单一所有者规则禁止网状连接结构,程序中的每个值都是某个树的成员,根在某个变量上。
Rust程序通常不显式地删除值,C和C++程序使用free和delete销毁一个堆数据的变量。在Rust中删除一个值的方法是以某种方式从所有权树中删除它:通过离开变量的作用域(见下文),或者从向量中删除元素,或者其他类似的行为。关键是,Rust确保某个值会连带它拥有的值一起销毁。
作用域、生命周期
在Rust的代码中,一个大括号{...}就是一个代码块(block)。在流程控制语句中,通常会有若干个代码块,例如if Condition {...}、while Condition {...},或loop {...}等等,在这些语句中,代码块用于组织代码,完成一个判断或循环功能。除此之外,代码块还起到区域隔离的作用:一个代码块看做一块区域,与另外的区域相互隔离,两个代码没有重叠的区域之间的变量和函数名不会冲突,区域中的变量都只在自己的区域内起到作用,因此代码块也可以叫做作用域。代码块可以嵌套,作用域也是嵌套的。当然一个源代码文件也是一个作用域,一个程序所有的代码文件是一个更大级别的作用域。
在一个作用域内声明的普通变量,仅存活于本作用域,代码运行到作用域结束,这个变量随即被销毁。从变量声明到此,就是这个变量的生命周期。根据Rust的安全原则,变量销毁的时候,归它所有的数据以及这些数据所有的数据,都会销毁。这样就不会有野指针(一般参考书都翻译成悬空指针)。
除了流程控制语句,也可以把任意一些代码用大括号括起来(不造成代码混乱的前提下),变为一个作用域,这样可以有效地控制变量的生存与毁灭!
对于一个值a,如果它有一个引用 r_a,那么 r_a必须在a进行初始化后指向它,在a销毁以前销毁(或同时到达作用域的末尾)。
RC和Arc
在Rust中,通常一个值只有一个所有者,一个所有者可以拥有多个值,当所有者超出作用域销毁后,值也被销毁。这样在某些情况下稍显死板,所以出现了一种引用计数型指针Rc,可以延长数据的生命。这有点像Java或C#中的垃圾回收机制,某个值可以有多个Rc指针指向它,只要是有一个这样的指针存在,这个值就不会销毁,直到所有指针都销毁了,这个值才被销毁。
use std::rc::Rc; //use 是引入rc模块的RC类型,就像C#的using和python的import // Rc指针可以包装任何类型的值,通过clone()方法复制出多个同目标值的Rc指针
let s: Rc<String> = Rc::new("山神庙".to_string()); //创建Rc指针
let t: Rc<String> = s.clone();
let u: Rc<String> = s.clone();
Rc指针可以自动解引用,调用所包装的类型的方法,此例中可以直接在Rc<String>类型的s、t、u上使用String的任何常用方法。
因为Rc是共享指针(多个指针共享一份数据),所以是不可变类型,一经创建就不能通过它修改所包装的值,也不可以把指针指向一个新值。
use std::rc::Rc; let s: Rc<String> = Rc::new("白衣秀士王伦".to_string()); //创建Rc指针
s.push('气'); //错误,Rc指针的目标值不可变
s = Rc::new("托塔天王晁盖".to_string()); //错误,不允许重新赋值
关于所有权,就先介绍这么多。
再次强调一下,对于初学者,通常把所有权随意转移,也通常不注意引用的生命周期必须必目标值的生命周期短,尤其是在函数调用中。
要养成先规划各变量的生命周期的习惯,学Rust不仅学语法,还提高了程序规划的能力。。。。。。
所有权这个概念,真是难者不会,会者不难,关键是要理解透彻。也许我的理解某些地方也有偏差,在以后深入学习中,不断进步吧!
Rust之路(4)——所有权的更多相关文章
- Rust之路(0)
Rust--一个2012年出现,2015年推出1.0版本的"年轻"语言.在 2016 至 2018 年的 stack overflow 开发人员调查中,被评比为 "最受欢 ...
- Rust之路(2)——数据类型 上篇
[未经书面同意,严禁转载] -- 2020-10-13 -- Rust是系统编程语言.什么意思呢?其主要领域是编写贴近操作系统的软件,文件操作.办公工具.网络系统,日常用的各种客户端.浏览器.记事本. ...
- Rust之路(3)——数据类型 下篇
[未经书面同意,严禁转载] -- 2020-10-14 -- 架构是道,数据是术.道可道,非常道:术不名,不成术!道无常形,术却可循规. 学习与分析数据类型,最基本的方法就是搞清楚其存储原理,变量和对 ...
- Rust之路(1)
[未经书面许可,严禁转载]-- 2020-10-09 -- 正式开始Rust学习之路了! 思而不学则罔,学而不思则殆.边学边练才能快速上手,让我们先来个Hello World! 但前提是有Rust环境 ...
- Writing A Threadpool in Rust
文 Akisann@CNblogs / zhaihj@Github 本篇文章同时发布在Github上:https://zhaihj.github.io/writing-a-threadpool-in- ...
- Rust 入门 (四)
所有权是 rust 语言独有的特性,它保证了在没有垃圾回收机制下的内存安全,所以理解 rust 的所有权是很有必要的.接下来,我们来讨论所有权和它的几个特性:借用.切片和内存结构. 什么是所有权 Ru ...
- 刷完欧拉计划中难度系数为5%的所有63道题,我学会了Rust中的哪些知识点?
我为什么学Rust? 2019年6月18日,Facebook发布了数字货币Libra的技术白皮书,我也第一时间体验了一下它的智能合约编程语言MOVE,发现这个MOVE是用Rust编写的,看来想准确理解 ...
- Rust入坑指南:朝生暮死
今天想和大家一起把我们之前挖的坑再刨深一些.在Java中,一个对象能存活多久全靠JVM来决定,程序员并不需要去关心对象的生命周期,但是在Rust中就大不相同,一个对象从生到死我们都需要掌握的很清楚. ...
- FinClip小程序+Rust(三):一个加密钱包
一个加密货币钱包,主要依赖加密算法构建.这部分逻辑无关iOS还是Android,特别适合用Rust去实现.我们看看如何实现一个生成一个模拟钱包,准备供小程序开发采用 前言 在之前的内容我们介绍了整 ...
随机推荐
- 再试Count(*) 与Count(*) 列
试问,如果有一张表有两个字段,均可为空,插入两条首个字段为空的记录,再插入两条第二字段为空的记录,问count(*)和count(列)结果如何? 答案:count(*)是正常的四条,而count(列) ...
- 为什么ping不通google.com
前言 为什么在ping不通Google的时候,我们却可以web直接访问Google (已开启SSR 翻 墙) SSR访问Google 因为GFW的限制导致国内无法直接访问谷歌,那么SSR为什么能绕过限 ...
- 别再眼高手低了! 这些Linq方法都清楚地掌握了吗?
不要再眼高手低了,这些Enumerable之常见Linq扩展方法都清楚掌握了吗?其实这是对我自己来说的! 例如:一个人这个技术掌握了一点那个技术也懂一点,其他的好像也了解一些,感觉自己啥都会一点,又觉 ...
- 秒懂JVM的三大参数类型,就靠这十个小实验了
秒懂JVM的三大参数类型,就靠这十个小实验了 你好,我是悟空哥,「7年项目开发经验,全栈工程师,开发组长,超喜欢图解编程底层原理」.手写了2个小程序,Java刷题小程序,PMP刷题小程序,已发布到公众 ...
- [LeetCode]678. 有效的括号字符串、20. 有效的括号(栈)
题目 678. 有效的括号字符串 给定一个只包含三种字符的字符串:( ,) 和 *,写一个函数来检验这个字符串是否为有效字符串.有效字符串具有如下规则: 任何左括号 ( 必须有相应的右括号 ). 任何 ...
- StringBuilder 比 String 快?空嘴白牙的,证据呢!
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 聊的是八股的文,干的是搬砖的活! 面我的题开发都用不到,你为什么要问?可能这是大部分 ...
- k8s service对象
k8s service对象 概述 service服务也是Kubernetes里核心字眼对象之一,Kubernetes里的每一个service其实就是我们经常提起的微服务架构中的一个微服务,之前讲解 ...
- ES6重度学习 demo实例
let 与 const // 并非真正的常量 // const 的本质: const 定义的变量并非常量,并非不可变, // 它定义了一个常量引用一个值.使用 const 定义的对象或者数组,其实是可 ...
- matlab数字图像简单的加密方法
图像加密的重要性可想而知,每个人都会有自己的小秘密,通过图像加密的方法可以保护自己的照片等的安全. 一般情况下,图像加密可以分为以下几个步骤: 1.选择图像加密算法 2.根据算法获取秘钥 3.根据保存 ...
- 理解Java中的final关键字
final关键字的基本用法 1. 修饰类 出于安全考虑,类无法被继承 2. 修饰方法 防止继承类修改方法private方法会隐式指定为final方法: 3. 修饰变量 基本数据类型,初始化后不能再修改 ...