【未经书面同意,严禁转载】 -- 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)——所有权的更多相关文章

  1. Rust之路(0)

    Rust--一个2012年出现,2015年推出1.0版本的"年轻"语言.在 2016 至 2018 年的 stack overflow 开发人员调查中,被评比为 "最受欢 ...

  2. Rust之路(2)——数据类型 上篇

    [未经书面同意,严禁转载] -- 2020-10-13 -- Rust是系统编程语言.什么意思呢?其主要领域是编写贴近操作系统的软件,文件操作.办公工具.网络系统,日常用的各种客户端.浏览器.记事本. ...

  3. Rust之路(3)——数据类型 下篇

    [未经书面同意,严禁转载] -- 2020-10-14 -- 架构是道,数据是术.道可道,非常道:术不名,不成术!道无常形,术却可循规. 学习与分析数据类型,最基本的方法就是搞清楚其存储原理,变量和对 ...

  4. Rust之路(1)

    [未经书面许可,严禁转载]-- 2020-10-09 -- 正式开始Rust学习之路了! 思而不学则罔,学而不思则殆.边学边练才能快速上手,让我们先来个Hello World! 但前提是有Rust环境 ...

  5. Writing A Threadpool in Rust

    文 Akisann@CNblogs / zhaihj@Github 本篇文章同时发布在Github上:https://zhaihj.github.io/writing-a-threadpool-in- ...

  6. Rust 入门 (四)

    所有权是 rust 语言独有的特性,它保证了在没有垃圾回收机制下的内存安全,所以理解 rust 的所有权是很有必要的.接下来,我们来讨论所有权和它的几个特性:借用.切片和内存结构. 什么是所有权 Ru ...

  7. 刷完欧拉计划中难度系数为5%的所有63道题,我学会了Rust中的哪些知识点?

    我为什么学Rust? 2019年6月18日,Facebook发布了数字货币Libra的技术白皮书,我也第一时间体验了一下它的智能合约编程语言MOVE,发现这个MOVE是用Rust编写的,看来想准确理解 ...

  8. Rust入坑指南:朝生暮死

    今天想和大家一起把我们之前挖的坑再刨深一些.在Java中,一个对象能存活多久全靠JVM来决定,程序员并不需要去关心对象的生命周期,但是在Rust中就大不相同,一个对象从生到死我们都需要掌握的很清楚. ...

  9. FinClip小程序+Rust(三):一个加密钱包

    ​ 一个加密货币钱包,主要依赖加密算法构建.这部分逻辑无关iOS还是Android,特别适合用Rust去实现.我们看看如何实现一个生成一个模拟钱包,准备供小程序开发采用 前言 在之前的内容我们介绍了整 ...

随机推荐

  1. python连接websocket wss

    def websocket_wss(): try: wss = create_connection(wss_url, timeout=10) if wss.status == 101: wss.sen ...

  2. 【机器学习】:Kmeans均值聚类算法原理(附带Python代码实现)

    这个算法中文名为k均值聚类算法,首先我们在二维的特殊条件下讨论其实现的过程,方便大家理解. 第一步.随机生成质心 由于这是一个无监督学习的算法,因此我们首先在一个二维的坐标轴下随机给定一堆点,并随即给 ...

  3. 福利来了~Linux一键部署包,环境安装不用愁!!!

    前言 昨天一哥们的弟弟突然问我有没有部署过的Linux,公司连个运维都没有,服务器都要后端部署.... 你有没有相似的遭遇呢?公司规模小,后端即是运维,一份工资干两份活,哈哈~ 为了解决这老弟的困惑, ...

  4. JsonPath使用教程

    application/json标识Json数据格式,是Http请求常见的一种Content-Type.我们经常也会看到接口返回数据类型为json格式.功能测试/自动化脚本里,经常会需要提取json数 ...

  5. IOC 原理

    SpringIOC实现原理 1. 依赖倒置 假设我们设计一辆汽车:先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车.这里就出现了一个“依赖”关系:汽车依赖车身,车 ...

  6. linux之磁盘整理

    yum安装的时候报错没有磁盘空间,需要整理 就需要查看下目前的磁盘使用情况 首先是df -hl查看从磁盘使用 可以到根目录下看看文件夹大小 du -sh * 或者下面的命令 接下来我们要删除东西整理磁 ...

  7. zookeeper(5) 客户端

    zookeeper客户端主要负责与用户进行交互,将命令发送到服务器,接收服务器的响应,反馈给用户.主要分为一下三层: 用户命令处理层 用户命令处理层的功能是读取用户输入的命令,解析用户命令和输入参数, ...

  8. Java基础一篇过(五)Map这篇就够了

    文章更新时间:2020/03/03 一.Map介绍 Map是Java的一个接口,没有继承,以Key--Value的形式来储存元素信息,常用到的有3个子类实现: HashMap 底层数据结构是散列桶(数 ...

  9. vue入门(一)----工程vue_sell

    1.利用vue脚手架工具创建vue,webpack工程目录 npm install -g vue-cli  安装vue脚手架 vue init webpack sell 初始化一个webpack模板, ...

  10. el-table行点击事件row-click与列按钮事件冲突

    需求简述 表格用el-table实现,操作列的编辑按钮点击事件正常实现.现要为行加一点击事件,即row-click.加上后,发现点击操作列的编辑按钮时,会触发按钮本身事件,同时会触发行点击事件.第一版 ...