【未经书面同意,严禁转载】 -- 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. PHP 安装 扩展时 抛出 /usr/local/Cellar/php@7.1/7.1.25/pecl 异常解决

    liugx@MacBook-Pro  ~/work/php/ext_source/php-xhprof-extension   master  make installmkdir: /usr/ ...

  2. leetcode题目,个人见解1

    给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一. 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字. 你可以假设除了整数 0 之外,这个整数不会以零开头. 示例 1: 输入 ...

  3. 为ASP_NET应用程序启用SQL缓存

    步骤一: sql数据库必须开启ServiceBroker服务,首先检测是否已经启用ServiceBroker,检测方法: SELECT DATABASEPROPERTYEX('dbName','IsB ...

  4. java输出1-100之间的数并求和for+while+do while实现

    public static void main(String args[]) {//do while int sum = 0; //当前之和 int i = 1; //加数 do { if (i%2= ...

  5. [程序员代码面试指南]栈和队列-最大值减去最小值 小于或等于num 的子数组的数量(单调队列)

    题目 给定数组arr和整数num,求数组的子数组中有多少个的满足"最大值减去最小值<=num". 解题思路 分析题目,有结论: 如果数组arr[i...j]满足条件,则它的每 ...

  6. 【高并发】面试官:Java中提供了synchronized,为什么还要提供Lock呢?

    写在前面 在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块.既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢?这是 ...

  7. hadoop和hbase高可用模式部署

    记录apache版本的hadoop和hbase的安装,并启用高可用模式. 1. 主机环境 我这里使用的操作系统是centos 6.5,安装在vmware上,共三台. 主机名 IP 操作系统 用户名 安 ...

  8. Vue等待父组件异步请求回数据'后'传值子组件

    问题: 让子组件在父组件有哪个数据的时候再渲染, 解决方案: 可以在父组件上加一个判断条件, 举例说明: <a-component :opt="opt" v-if=" ...

  9. 集群数据ID生成之美团叶子生成

    转自https://tech.meituan.com/2017/04/21/mt-leaf.html 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识.如在美团点评的金融.支付.餐饮.酒店. ...

  10. hystrix源码之概述

    概述 hystrix核心原理是通过代理执行用户命令,记录命令执行的metrics信息,通过这些metrics信息进行降级和熔断. 源码结构包括一下几个部分: 熔断器 熔断器就是hystrix用来判断调 ...