所有权

  • 所有权是rust最独特的特性,它让rust无需GC就可以保证内存安全。

什么事所有权

  • rust的核心特性就是所有权
  • 所有程序在运行时都必须管理他们使用计算机内存的方式
  1. 有些语言有垃圾收集机制,在程序运行时它们会不同地寻找不再使用的内存
  2. 在其他语言中,程序员必须显式地分配和释放内存
  • rust采用了第三种方式
  1. 内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则
  2. 当程序运行时,所有权特性不会减慢程序的运行速度。

stack(栈) vs heap(堆)

  • 在像rust这样的系统级编程语言里,一个值是在stack上还是在heap上对语言的行为和你为什么要做某些决定是有很大的影响的
  • 在你的代码运行的时候,stack和heap都是你可用的内存,但他们的结构很不相同。

stack vs heap 存储数据

  • stack按值的接受顺序存储,按相反的顺序将他们移除(后进先出,LIFO)
  1. 添加数据叫做压入栈
  2. 移除数据叫做弹出栈
  • 所有存储在stack上的数据必须拥有已知的固定的大小
  1. 编译时大小未知的数据或运行时大小可能变化的数据必须存放在heap上
  • heap内存组织性差一些
  1. 当你把数据放入heap时,你会请求一定数量的空间
  2. 操作系统在heap里找到一块足够大的空间,把它标记在用,并放回一个指针,也就是这个空间地址
  3. 这个过程叫做在heap上进行分配,有事仅仅成为“分配”
  • 把值压到stack上不叫分配
  • 因为指针是已知固定大小的,可以把指针存放在stack上。
  1. 但如果想要实际数据,你必须使用指针来定位。
  • 把数据压到stack上要比在heap上分配快的多
  1. 因为操作系统不需要寻找用来存储新数据的空间,那个位置永远都在stack的顶端
  • 在heap上分配空间需要做更多的工作
  1. 操作系统首先需要找到一个足够大的空间来存放数据,然后要做好记录方便下次分配

stack vs heap 访问数据

  • 访问heap中的数据要比访问stack中的数据慢,因为需要通过指针才能找到heap中的数据
  1. 对于现代的处理器来说,由于缓存的缘故,如果指令在内存中跳转的次数越少,那么速度就越快
  • 如果数据存放的距离比较近,那么处理器的处理速度集合更快一下(stack上)
  • 如果数据直接的距离比较远,那么处理速度就会慢一下(heap上)
  1. 在heap上分配大量空间也是需要时间的

stack vs heap 函数调用

  • 当你的代码调用函数是,值被传入到函数(也包括指向heap的指针)。函数本地的变量被压到stack上。当函数结束后,这些值会从stack上弹出

stack vs heap 所有权存在的原因

  • 所有权解决的问题
  1. 跟踪代码的那些部分正在使用heap的哪些数据
  2. 最小化heap上的重复数据量
  3. 清理heap上为使用的数据以避免空间不足。
  • 一旦你懂的了所有权,那么就不需要经常去想stack或heap了。
  • 但是知道管理heap数据是所有权存在的原因,这个有助于解释它为什么会这样工作。

所有权规则

  • 每个值都有一个变量,这个变量是该值的所有者
  • 每个值同时只能有一个所有者
  • 当所有者超出作用域(scope)时,该值将被删除

变量作用域

  • scope就是程序中一个项目的有效范围
fn main(){// s 不可用let s = "hello";    // s 可用// 可以对 是进行相关操作
} // s 作用域到此结束,s 不再可用

string 类型

  • string 比那些基础标量数据类型更复杂
  • 字符串字面值:程序里手写的那些字符串值。他们是不可变的
  • rust还有第二种字符串类型:String
  1. 在heap上分配。能够存储在编译时未知数量的文本。

创建string类型的值

  • 可以使用from函数从字符串字面值创建string类型
//"::"表示from是string类型下的函数
let s = String::from("hello");
  • 这类字符串是可以被修改的
fn main(){let mut str = String::from("hello");str.push_str(", world");println!("{}", str);
}
  • 为什么String类型的值可以修改,而字符串字面值不能被修改?

内存和分配

  • 字符串字面值,在编译时就知道它的内容了,其文本内容直接被硬编码到最终的可执行文件里
  1. 速度快、高效。是因为其不可变性。
  • String类型,为了支持可变性,需要在heap上分配内存来报错编译时位置的文本内容:
  1. 操作系统必须在运行时来请求内存
  2. 这步通过调用String::from来实现
  • 当用完String之后,需要使用某种方式将内存返回给操作系统
  1. 这步,在拥有GC的语言中,GC会跟踪并清理不再使用的内存
  2. 没有GC,就需要我们去识别内存何时不再使用,并调用代码将它返回。
  3. 如果忘记了,那就浪费内存
  4. 如果提前做了,变量就会非法
  5. 如果做了两次,也是bug,必须一次分配对应一次释放
  • rust采用不同的方式:对于某个值来说,当拥有它的变量走出作用范围时,内存会立即自动的交给操作
  • drop函数

变量和数据交互的方式:移动(move)

  • 多个变量可以与同一个数据使用一种独特的方式来交互
let x = 8;
let y = x;
  • 整数是已知且固定大小的简单的值,这个两个8被压到了stack中

变量和数据交互的方式:移动(move) String版本

let s1 = String::from("hello");
let s2 = s1;
  • 一个String由3部分组成:
  1. 一个执行存放字符串内容的内存的指针
  2. 一个长度
  3. 一个容量
name value
prt
len 5
capacity 5
index value
0 h
1 e
2 l
3 l
4 o
  • 上面这些东西都是反正stack上。
  • 存放字符串内容的部分在heap上
  • 长度len,就是存放字符串内容所需要的字节数
  • 容量capacity是指String从操作系统总共获取的内存总字节数
  • 当把s1赋给s1,String的数据被复制了一份
  1. 在stack上复制了一份指针、长度、容量
  2. 并没有复制指针所指向的heap上的数据
let s1 = String::from("hello");
let s2 = s1;
  • 当变量离开作用域时,rust会自动调用drop函数,并将变量使用的heap内存释放。
  • 当s1、s2离开作用域时,它们都会尝试释放相同的内存
  1. 二次释放(double free)bug
  • 为了保证内存安全
  1. rust没有尝试复制被分配的内存
  2. rust让s1失效
  3. 当s1离开作用域的时候,rust不需要释放任何东西
  • 试试看当s2创建以后,再使用s1是什么效果
fn main(){let s1 = String::from("hello");let s2 = s1;println!("{}, {}", s1, s2);
}
  • 浅拷贝
  • 深拷贝
  • 你也许会将复制指针、长度、容量视为浅拷贝,但由于rust让s1失效了,所以我们用一个新的术语:移动(move)
  • 隐含的一个设计原则:rust不会自动创建数据的深拷贝
  1. 就运行时性能而言,任何自动赋值的操作都是廉价的

变量和数据交互的方式:克隆(clone)

  • 如果真想对heap上面的String数据进行深度拷贝,而不仅仅是stack上的数据,可以使用clone方法
fn main(){let s1 = String::from("hello");let s2 = s1.clone();println!("{}, {}", s1, s2);
}

stack上的数据:复制

fn main(){let x = 1;let y = x;println!("{}, {}", x, y);
}
  • copy trait,可以用于像整数这样完全存放在stack上面的类型
  • 如果一个类型实现了copy这个trait,那么旧的变量在复制后仍然用
  • 如果一个类型或者改类型的一部分实现了drop trait,那么rust不允许让他再去实现copy trait了

一些拥有copy trait的类型

  • 任何简单标量的组合类型都可以是copy的
  • 任何需要分配内存或某种资源的都不是copy的
  • 一些拥有copy trait的类型
  1. 所有的整数类型,例如u32
  2. bool
  3. char
  4. 所有的浮点类型,例如f64
  5. tuple(元组),如果其所有的字段都是copy的
  6. (i32, i32) 是
  7. (i32, String) 不是

所有权与函数

  • 在语义上,将值传递给函数和把值赋给变量是类似的
  1. 将值传递给函数将发送移动或复制
fn main(){let s = String::from("hello world");take_ownership(s);  // s作为实参,s移动后,失效let x = 4;makes_copy(x);      // x 作为实参,x拷贝,不失效println!("x:{}", x);// println!("s:{}", s);
}fn take_ownership(some_string: String){println!("{}", some_string);    // 方法结束后  some_string 失效
}fn makes_copy(some_number: i32){println!("{}", some_number);  // 方法结束后  some_number 失效
}

返回值与作用域

  • 函数在返回值的过程中同样也会发生所有权的转移
  • 一个变量的所有权总是遵循同样的模式
  1. 把一个值赋给其他变量时就会发生转移
  2. 当一个包含heap数据的变量离开作用域时,他的值会被drop函数清理,除非数据的所有权转移到另外一个变量上了
fn main(){let s1 = gives_ownership();let s2 = String::from("hello_2");let s3 = takes_and_gives_back(s2);      //a_string实参数 传入函数,移动,失效println!("s1:{}", s1);// println!("s2:{}", s2);println!("s4:{}", s3);
}fn gives_ownership()-> String{let some_string = String::from("hello_1");some_string         // some_string 作为函数返回值,移动,失效
}fn takes_and_gives_back(a_string: String)-> String{a_string       // a_string参数 作为函数返回值,移动,失效
}

如何让函数使用某个值,但不获得某所有权?

  • rust有一个特性叫做“引用(reference)”
fn main(){let s1 = String::from("hello");let (s2, len) = calculate_length(s1);// print!("s1:{}", s1);print!("s2:{}, len:{}", s2, len);
}fn calculate_length(s: String)-> (String, usize){let length = s.len();(s, length)
}

引用和借用

  • 参数的类型是&String而不是String
  • &符号就表示引用:允许你应用某些值而不取得其所有权
fn main(){let s1 = String::from("hello");// s1作为引用,传递到方法里面// 方法参数没有s1所有权,所以,s1不会失效let len = calculate_length(&s1);  print!("s1:{}, len:{}", s1, len);
}fn calculate_length(s: &String)-> usize{// 不能修改借用的东西// s.push_str(", world");s.len()
}

借用

  • 我们吧引用作为函数参数这个行为叫做借用
  • 是否就可以修改借用的东西(不行)
  • 和变量一样,引用默认也是不可变得

可变引用

fn main(){let mut s1 = String::from("hello");// s1作为引用,传递到方法里面// 方法参数没有s1所有权,所以,s1不会失效let len = calculate_length(&mut s1);  print!("s1:{}, len:{}", s1, len);
}fn calculate_length(s: &mut String)-> usize{s.push_str(", world");s.len()
}
  • 可变引用有一个重要的限制:在特定作用域内,对某一块数据,只能有一个可变的引用
fn main(){let mut s = String::from("hello");let s1 = &mut s;// let s2 = &mut s;println!("s1:{}", s1);// println!("s2:{}", s2);
}
  1. 这样做的好处是可在编译时防止数据竞猜
  • 一下三种行为下回发生数据竞争
  1. 两个或多个指针同时访问多个数据
  2. 至少有一个指针用于写入数据
  3. 没有使用任何机制来同步对数据的访问
  • 可以通过创建新的作用域,来允许非同时的创建多个可变引用
fn main(){let mut s = String::from("hello");{let s1 = &mut s;println!("s1:{}", s1);}let s2 = &mut s;println!("s2:{}", s2);
}

另外一个限制

  • 不可以同时拥有一个可变引用和一个不可变的引用
  • 多个不可变的引用是可以的
fn main(){let mut s = String::from("hello");let s1 =  &s;let s2 =  &s;// let s3 = &mut s;println!("s1:{}", s1);println!("s2:{}", s2);// println!("s3:{}", s3);
}

悬空引用dangling references

  • 悬空指针(dangling pointer):一个指针引用了内存中的某个地址,而这块内存可能已经释放并分配给其它人使用了。
  • 在rust里,编译器可保证引用永远都不是悬空引用;
fn main(){let r = dangle();
}fn dangle()-> &String{let s = String::from("hello");&s      // s 在方法调用结束后失效,返回一个失效的引用
}

引用的规则

  • 在任何给定的时刻,只能满足下列条件之一
  1. 一个可变的引用
  2. 任意数量不可变的引用
  • 引用必须一直有效

rust的所有权与引用相关推荐

  1. Rust的所有权(Ownership)

    1. 什么是Ownership Rust的所有权,是一个跨时代的理念,是内存管理的第二次革命.Ownership是Rust的一个核心概念. 每种编程语言都有自己的一套内存管理的方法.有些需要显式的分配 ...

  2. 理解Rust的所有权

    Time: 20190921 所有权是Rust中最独特的特征,有了它就能保证Rust内存安全,且无需垃圾回收机制.因此,理解Rust的所有权机制非常重要.和所有权一起讲到的其他几个概念是: 引用,借用 ...

  3. iOS内存管理系列之一:对象所有权与引用计数

    原文地址:[转]iOS内存管理系列之一:对象所有权与引用计数作者:anynot 内存管理是iPhone或iPad开发中最为重要的一部分.掌握好了内存管理,开发出的应用就能运行流畅:掌握不好,开发出的东 ...

  4. Rust的所有权与可变性

    Rust与其他语言的比较 文章目录 Rust与其他语言的比较 特性 所有权 直接转移 间接转移 引用.借用 可变性与不可变性 特性 所有权 在Rust中,若声明有类似于Java或C++中的引用传递类型 ...

  5. 007 Rust死灵书笔记之引用与别名

    介绍 本系列录制的视频主要放在B站上Rust死灵书学习视频 Rust相关的源码资料在:https://github.com/anonymousGiga 笔记内容 引用与别名 在Rust中,存在两种引用 ...

  6. 我为什么选择Rust

    文章目录 不堪回首的往事 选择的方向 Go 优点 缺点 Rust 优点 缺点 结论 不堪回首的往事 七八年前,我自认还是一名合格的C/C++开发者.后来涉及大数据处理方向,被Python的魅力所吸引, ...

  7. Rust是如何实现内存安全的--理解RAII/所有权机制/智能指针/引用

    不带自动内存回收(Garbage Collection)的内存安全是Rust语言最重要的创新,是它与其他语言最主要的区别所在,是Rust语言设计的核心. Rust希望通过语言的机制和编译器的功能,把程 ...

  8. rust编程-rust所有权理解(chapter 4.2 引用实质是借用)

    目录 2. 引用与借用 2.1 可变(mutable)引用 2.2 悬空(dangling)引用 2.3 引用的规则总结 2. 引用与借用 上一章节中提到,所有权在函数调用中的转移,函数返回必须同时返 ...

  9. Rust 所有权介绍

    所有权对大多数开发者而言是一个新颖的概念,它是 Rust 语言为高效使用内存而设计的语法机制.所有权概念是为了让 Rust 在编译阶段更有效地分析内存资源的有用性以实现内存管理而诞生的概念. 计算机程 ...

最新文章

  1. c#和python_IronPython和C#交互
  2. 鸟哥的Linux私房菜(基础篇)- 一个简单的 SPFdisk 分割实例
  3. merge k sorted lists java_LeetCode 第23题 Merge k Sorted Lists【分而治之】【最小堆】(Java)...
  4. java html api 百度云,Javase-6.0_中文API_HTML(最新更新)
  5. 算法岗面试前怎样高效刷题?
  6. MNIST机器学习入门
  7. 《跑跑卡丁车》国内运营商裁员超30%
  8. ajax的两个重要参数contentType 和dataType
  9. shell第四次练习
  10. python多线程互斥锁_Python中线程互斥锁是什么
  11. 计算机等级考试oracle,用spt更新ORACLE
  12. 【ML小结2】信息论
  13. 求长方形和正方形的周长
  14. 全国计算机二级flash,教你如何在Flash当中制作插按钮动画
  15. 三菱FX3U源码在V10.5的基础上增加了禁止上传功能
  16. Ubuntu 20下pycharm无法使用中文输入法
  17. [轻音乐] - 班得瑞专辑[14CD]
  18. java.lang.NoSuchMethodException: com.cbb.qqzone.pojo.Topic.<init>(java.lang.Integer)
  19. 友盟集成QQ第三方登录
  20. 1502: [NOI2005]月下柠檬树

热门文章

  1. 练习:测测你优势教养的程度
  2. 如何解决商品秒杀超卖问题
  3. 跳转到三方App ,三方App 是如何返回自己的App的
  4. 【聊技术】在Android中实现自适应文本大小显示
  5. 指数函数误差平方和matlab,数值分析与实验数学081 张燃 3080801119).doc
  6. Vue表格绑定数据、添加数据
  7. Android 入门第九讲01-音频(本地音乐播放,暂停,继续播放,获取播放时间,快进到指定位置,变速播放,播放data/data/目录下的音频文件,播放网络歌曲)
  8. html页面用excel打印,excel怎么打印不能全部显示出来
  9. 谁教会老公出轨外面养情人
  10. admin.php生成地址,FastAdmin隐藏后台登录入口地址的方法