Rust
第四章 所有权
所有权是Rust最独特的特性,他让rust不用GC就可以保证内存安全。
4.1.1 什么是所有权
- Rust的核心特性就是所有权
- 所有程序在运行时必须管理他们使用计算机内存的方式
- 有一些语言有垃圾收集机制,在程序运行时,他们会不断地寻找不再使用的内存
- 在其他语言中,程序员必须显示的分配和释放内存:如C,malloc以及free
- 在rust中采用了第三种方式:
- 内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则,并且不会产生任何开销,也就是说这一特性,并不会减慢程序的速度。
stack VS heap
- stack 存储数据的方式是顺序的,并且遵循LIFO
- 并且所有存储咋stack上面的数据必须拥有已知且固定的大小。
- 也就是说,编译时大小未知的或者运行时可能发生改变的都应该放在heap上面。
- 但是heap的内存组织性会差一些:
- 当你把数据放入heap中时,你会申请一块足够大的空间,并标记为使用中,然后返回一个指针,作为这个空间的地址。
- 这个过程叫在heap上进行的分配,简:”分配“。
这一部分知识与pwn基础知识大量重合,就不记录了。
主要就是,函数调用栈以及堆相关内容。
所有权存在的原因
- 所有权解决的问题:
- 跟踪代码哪些部分在使用heap 哪些数据
- 最小化heap上面的重复数据
- 清理heap上个面的未使用数据以避免空间不足
总之,管理heap数据才是所有权出现的原因
4.1.2 所有权规则
- 每个值都只能有一个变量,这个变量是该值的所有者
- 每个值同时只能有一个所有者
- 当所有者超出作用域(scope)时,该值被删除。
变量作用域
注意变量的作用域仅仅只在声明变量之后到花括号结束点,这么一段范围。
String类型
- string类型存储在heap上面
- 字符串字面值:程序里面写死的字符串值,不可改变。
- 能够在heap上面分配,能够存储在编译时未知数量的文本。
创建String类型的值
可以使用from函数从字符串字面值
其中,”::“表示from时String类型下的关联函数;
并且这类字符串是可以修改的
- 为什么String类型的值可以修改,而字符串字面值却不能修改?
因为他们之间处理内存的方式不同。
内存和分配
- 字符串字面值,在编译时就知道它的内容了,其文本内容直接被硬编码到最终的可执行文件中,
- String 类型,为支持可变性,需要在heap上面使用某种方式返回给操作系统
- 这一步,在拥有Gc的语言之后,GC会跟踪并清理不再使用的内存
- 如果没有GC,就需要我们去辨识内存何时不再使用,并调用代码将他返回。
- RUST采用了不同的操作方式,当变量走出其作用范围后,就会收回他的内存。
变量和数据交互的方式:移动(MOVE)
但如果这里的类型是String类型
String类型
- 一个String类型有3部分组成,一个 指向存放字符串内容的指针,一个 表示该字符串长度的值,一个 表示该字符串容量的值,而这些都存放在 Stack上面。内容则全部存放在,heap上面。
- 而我们知道 在rust里面,如果一个变量走出了他的作用域,Rust就会调用一个Drop函数来释放他的内存,而这里我们就会遇到一个问题,如果let x = String::from("hello");let y = x;这里的y就会copy一份x的Stack上面的三个值,而不会copyheap上面的值,所以在同一个作用域中,就有了两个指向同一个区域的指针,在该作用域结束时,他们都会被回收,这也就代表着,这一块heap上面的区域,会被释放两次,也就会造成double free
这样肯定是不可以的,所以,Rust在这里引出了一个重要的概念 MOVE移动。
也就是说当我进行如上的赋值操作后,x会直接失效,y会代替x存在,而当作用域结束后,x并不会释放,那一块heap区域,完全交由y来释放。
这里就要引出一个新的概念:
浅拷贝与深拷贝
我们一般认为,这样单纯复制指针,长度,容量的操作为浅拷贝,它不会复制实际的在heap上面的内容。
这里也隐含了一个RUst的设计原则:RUST不会自动的创建数据的深拷贝。
当然如果真的想对heaps上面的数据进行深度拷贝操作,而不仅仅是stack上面的数据,我们可以使用clone操作。
Stack上的copy(复制)
这里的复制操作,就是简单的操作,不存在浅拷贝和深拷贝。
- Copy trait,可以用于像整数这样完全村存放在Stack上面的类型
- 如果一个类型实现了Copy这个Trait,那么旧的变量再赋值后仍然可用
- 如果一个类型或者该类型的一部分实现了Drop trait,那么Rust不允许让他再去实现Copy Trait
哪些类型可以实现copy trait
- 所有标量的组合
- 所有的整数类型
- bool
- char
- 浮点类型
- Tuple(所有字段值均可实现copy trait)
4.1.3 所有权与函数
- 在语义上面将值传递给变量是类似的
- 注意,这里如果变量是上面几个说过的有copy trait的变量类型,那么他们就不会进行一个所有权的转移,仅仅只是一个浅拷贝,但如果是类似String类型的变量,那他在调用函数时,就会将所有权转移到被调用的函数中去。
返回值和作用域
- 函数在返回值的过程中同样也会发生所有权的转移
- 一个变量的所有权总是遵循着同样的模式:
- 当一个值赋给其他变量时就会发生移动
- 当一个包含heap数据的变量离开作用域时,他的值就会drop函数清理,除非数据被移动到另外一个变量中
如何让某个函数使用值,但不获得其所有权
- 第一种方法就是,先将变量正常传入然后利用返回值的所有权传递,传回新变量手中,这样虽然变量名发生里改变,但是所有权没变,作用域也没变。
4.2.1 引用与借用
引用
也就是说在引用时,回创建一个新变量内容是引用变量的指针。
借用
我们把引用作为函数的这个行为叫做借用
同样的借用是不可以修改原变量的,因为只是 借用。
可变引用
将变量设为可变,参数可变,引用时可变即可。·
但是,可变引用有一个限制:在特定的作用域内,对某一块数据,只能有一个可变引用。
这样做的好处是可以在编译时防止数据竞争。
- 发生数据竞争的条件:
- 两个或者多个指针同时访问一个数据
- 至少有一个指针用于写入
- 没有使用任何机制来同步对数据的访问
RUst在编译时就防止了这一种err发生
但是,我们只需要添加作用域即可:
可以看到这里我们只是添加了一对花括号而已,这样就完成了
第二个限制:不允许同时拥有可变引用和不可变引用:
悬空引用(dangling references)
fn main(){
let m = dangling();
}
fn dangling() -> &String{
let note = String::from("helloe ");
¬e
}
报错:
引用的规则
- 一个可变引用或者任意数量的不可变引用
- 引用必须一直有效
4.3 切片