当前位置: 首页 > news >正文

初识Rust语言的所有权概念

目前仅看了第二版的官方文档,记录一下初步印象,应该还有更深刻一致的解释,水平有限,仅供参考。
实验环境:ubuntu17.10,rust1.18,vscode1.14 + 扩展rust(rls)。
BTW,环境搭建顺利得令人意外,Rust工具链打造的简洁精美,原生支持git,安装只需一条命令:curl https://sh.rustup.rs -sSf | sh。

初步印象

数据竞争主要有三个条件:

  1. 两个或更多指针同时访问同⼀数据。
  2. ⾄少有⼀个指针被写⼊。
  3. 没有同步数据访问的机制。

R非常重视并发,根据官方介绍:Rust 是一门着眼于安全、速度和并发的编程语言。而并发需要解决的就是数据竞争问题,自然会非常重视数据的使用过程,说是小心翼翼不为过。因为数据要关联到有名变量才能使用,所以rust在语言层面上针对变量的使用引入了解决方法,主要涉及的语法有:

  1. 变量声明时,不可变(immutable,默认)、可变(mutable)
  2. 变量赋值时,所有权转移(move)、借用(borrow)

需要注意的是,所有权仅针对复杂类型变量(在语法上,是没有copy trait的类型),例如String、vect等在堆上存储数据的类型,而简单类型并不用考虑,如int、tuple、array等,原因就在于赋值时数据是如何拷贝的(虽然都是浅拷贝)。

如果熟悉浅拷贝、深拷贝的概念,自然了解,对于在堆上分配空间的复杂类型,浅拷贝会导致两个或更多变量/指针同时指向同⼀数据,若有变量/指针作写入操作,就会引起数据竞争问题。

首图

所以,Rust用可变/不可变、所有权、生命期等来破坏数据竞争的条件,而这些解决方案全部在编译期搞定!
当然,代价是难以快速验证想法,毕竟使用变量时要仔细了,否则编都编不过,期待最佳实践和IDE的支持。

基本概念

1. 不可变、可变

let x = 3;  // x 默认不可变
x = 4;  // 错误!
let x = 4;  // 正确!遮盖了原有的同名变量
let mut y = 3;  // y可变
y = 4;  // 正确!

2. 所有权转移(move)

fn test(v: String) { println!("fn: {}", v); }  // 函数
let x = String::from("hello");  // 所有者x(String类型)
let y = x;  // 数据的所有权转移给y!
let z = x; // 错误!x已不可用
test(y);  // 所有权转移,新的所有者是形参v!当函数执行完毕,v离开作用域时值被丢弃(drop)!
println!("var: {}", y);  // 错误!y已不可用

这难免有令人抓狂的感觉,还能不能愉快地玩耍了?这数据跑得跟兔子一样,想用的时候都不知道去哪了!还可能无意中跑到函数里直接躺尸!

3. 借用/引用(borrow)

那么,一个变量想多次使用怎么办?答案是可以借用:使⽤其值但不获取其所有权。

fn test1(v: String) { println!("fn: {}", v); } 
fn test2(v: &String) { println!("fn: {}", v); }  // 参数为引用类型
let s = String::from("hello");  // 所有者s(String类型)
let s1 = &s;  // 不可变借用(borrow)!
let s2 = &s;  // 借用
let s3 = s1;  // 借用
test2(s1);  // 借用
test1(*s1);  // 错误!借用者s1没有所有权,无法通过s1转移(cannot move out of borrowed content)。
println!("var: {}", s); // 正确

小结:个人感觉,所有权转移主要为并发服务,本身并不常用,毕竟数据经常要复用,没人乐意要一直提防着数据跑哪去了,尤其在函数调用时。既然如此,一般把所有者保持不变,多使用引用,主要体现在复杂数据结构和函数上。

进一步

但是,实际使用的情况会比较复杂,即是否可变与转移、借用三者相互影响(混用)的情况。
从数据竞争的角度:读读不冲突,但读写、写写会冲突(读即不可变,写即可变);从实现的角度:引用是基于所有权的。
因此,可以看看哪些对象会冲突:(所有者,引用) × (不可变,可变)

首先,是否可变和所有权没有关系。

let x = String::from("hello");
let mut z = x;  // 转移后变量x不可用
z.push_str(" z");  //正确
// 可变引用要用星号来获得引用的内容,不可变引用不需要。
let mut x = 5; 
let y = &mut x;
*y += 1;

虽然不可变引用(&T)没有所有权,不会导致值被误转移,但借用之时要求值不能变,这意味着此时:所有权不能转移、所有者不能改值、不能同时有可变引用!

let mut x = String::from("hello");
let y = &x;  // 不可变引用
let z = x;  // 错误
x.push_str(" x");  // 错误
let z = &mut x;  // 错误:可变引用

可变引用(&mut T)

可变引用使用上略复杂,概念上也没有太统一的理解,这里单独考查。
“可变权”即可变引用对数据的读写权,具有唯一性(只有一个可用的可变引用)和独占性(其它读、写统统无效),所以对编译影响相当大。可变引用的可变权和所有者对数据的所有权有相似性,因为可变权也有move行为。

注:官方文档里没有可变权的概念,但个人感觉,用这个概念比较好理解可变引用的使用,也许还有更本质的解释,特此说明。

可变权move的两种方式

let mut x = String::from("hello");  // 所有者x有可变权
// 1. 直接转移
let y = &mut x;  // 1. y为可变引用,可变权move自x
let z = y;  // 直接转移。z为可变引用
y.push_str(" y");  // 错误!y的可变权已move给z
z.push_str(" z");  // 正确
// 2. 间接转移
let mut y = &mut x;  // 2. y为可变引用,可变权move自x
let w = &mut y;  // 要求y可变。w为可变引用
w.push_str(" w");  // 正确
// 转移(函数)
fn test(i: &mut String) { 
  i.push_str(" i");   // 正确
}
let mut x = String::from("hello");  // 所有者x有可变权
test(&mut x);
x.push_str(" x");  // 正确!可变权已归还

可变引用若有写入操作则要求所有者可变。

let x = String::from("hello");  // x不可变
let mut z = &x;   // z为不可变引用
z.push_str(" z");  // 错误!
let w = &mut z;  // w为可变引用
w.push_str(" w");  // 错误!
let mut y = x;  // 所有权转移,y可变
let z = &mut y;   // z为可变引用,要求y可变
z.push_str(" z");  // 正确!
let w = &z;  // w 为不可变引用
w.push_str(" w");  // 错误!

总结:

因为都涉及到值的修改,可变引用的行为和所有者相似,而且可变权和所有权都是面向数据且唯一的。

所有者

  1. 有所有权,move后不再可用,当所有者生命期结束,值被丢弃。
  2. 读的时候类似不可变引用,写的时候类似可变引用。

可变引用(&mut T)

  1. 有可变权,move自被引用者,当可变引用生命期结束,可变权自动归还。
  2. 可变权的源头应该来自所有者,否则意义不大。
作者:amita
来源:51CTO

相关文章:

  • 《SEO的艺术(原书第2版)》——3.1 SEO从业者所能完成的策略性目标
  • 警告:未来互联网安全动荡
  • 《人民日报》教你如何正确使用路由器
  • 窥探“大数据”全貌(技术篇、产业篇、企业转型篇)
  • 揭秘大数据安全分析”架构”
  • 《深入理解Android:Telephony原理剖析与最佳实践》一3.1 何为同步和异步
  • 《C语言程序设计》一 2.5 案例学习——华氏温度与摄氏温度的转换
  • 安防傍上物联网能否迎来硬件免费时代?
  • 深入探析koa之异步回调处理篇
  • 方法和数组
  • 物联网网络技术市场的发展
  • centos中安装mysql
  • 日志收集+分析+报警 logstash
  • 请问一下发博中出现的问题
  • Educational Codeforces Round 26
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • 【附node操作实例】redis简明入门系列—字符串类型
  • Java超时控制的实现
  • Netty源码解析1-Buffer
  • scrapy学习之路4(itemloder的使用)
  • Spring Security中异常上抛机制及对于转型处理的一些感悟
  • vagrant 添加本地 box 安装 laravel homestead
  • Webpack 4x 之路 ( 四 )
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 初识MongoDB分片
  • 创建一种深思熟虑的文化
  • 技术发展面试
  • 浅谈JavaScript的面向对象和它的封装、继承、多态
  • 适配mpvue平台的的微信小程序日历组件mpvue-calendar
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 微信小程序--------语音识别(前端自己也能玩)
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 最简单的无缝轮播
  • Prometheus VS InfluxDB
  • 选择阿里云数据库HBase版十大理由
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • ###C语言程序设计-----C语言学习(3)#
  • #数学建模# 线性规划问题的Matlab求解
  • #我与Java虚拟机的故事#连载16:打开Java世界大门的钥匙
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (day6) 319. 灯泡开关
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (TOJ2804)Even? Odd?
  • (编译到47%失败)to be deleted
  • (一)基于IDEA的JAVA基础10
  • (转)shell调试方法
  • (转载)Linux 多线程条件变量同步
  • **python多态
  • .mat 文件的加载与创建 矩阵变图像? ∈ Matlab 使用笔记
  • .Net 4.0并行库实用性演练
  • @Autowired自动装配
  • @Data注解的作用
  • [Angular] 笔记 8:list/detail 页面以及@Input
  • [C++]C++基础知识概述
  • [CF494C]Helping People