Rust所有权
C/C++中我们对于堆内存通常需要自己手动管理,手动申请和释放,即便有了智能指针,对于效率的影响和安全性问题也没有完全解决
Rust为了高效的使用和管理内存,以及对安全性的考量,提出了所有权的概念以及一系列规则
所有权规则
所有权有三条核心规则
- Rust中的每个值都有一个隐含的变量,称为所有者
- 一个值 同一时刻只能有一个所有者
- 当所有者离开作用域时,值会被丢弃(调用drop函数释放资源)
1 | fn main() { |
作用域
这里的作用域和C/C++的作用域基本类似
1 | { |
内存和分配
Rust是静态语言,这意味着我们无法像C++那样运行时扩容,例如vector会在满时进行扩容
在C++中=
是赋值的意思,理解就是将一个变量的值,赋值给一个新的变量,这是一种拷贝语义(Copy)
但是在Rust中,变量和数据交互的方式主要是移动(Move)和克隆(Clone)
移动与克隆
如果你学过C++11,那你一定知道移动语义,例如移动拷贝或者移动赋值
这实际上是一种所有权的转移,主要是避免频繁申请和释放堆空间
但是有一些情况,我们并不希望只是所有权的转移,而是真的创建一个副本进行操作,这就需要使用clone()
方法
1 | fn main() { |
栈空间
在栈空间内,Rust变量“移动”的方式其实就是复制,因为栈空间内基本上都是基本数据类型的,通常占用空间和复制时间不会很久,就会是直接复制,这时候两个变量都是可以使用的
1 | let x = 1; |
“基本数据”类型有这些:
- 所有整数类型,例如 i32 、 u32 、 i64 等。
- 布尔类型 bool,值为 true 或 false 。
- 所有浮点类型,f32 和 f64。
- 字符类型 char。
- 仅包含以上类型数据的元组(Tuples)。
堆空间
String对象的hello
存储的位置可就不是栈空间了而是堆空间
例如
1 | let s1 = String::from("hello"); |
当执行到第二步时,s1就会把自己对hello字符串对所有权移交给s2,此时s1,就会失效
因此在移动之后,继续使用s1会报错
关于函数的所有权机制
作为参数
当一个变量作为参数传递给函数时,所有权应该怎么处理
1 | fn main() { |
作为返回值
1 | fn main() { |
引用与租借
这里的引用和C++中的引用是类似的,如果不了解C++的引用,也可以认为是一种指针
例如
1 | fn main() { |
按照原先的理解,s1内部会存一个指向”hello”的指针,s2内部其实也是一个指向”hello”的指针,但是s2是后来的,我们就认为s2是s1的一个引用,也就是别名
- 引用可以认为是单独的一种类型
- 引用不会获得值的所有权
- 引用只能租借(Borrow)值的所有权
- 当一个值被移动时,原先的引用会失效,必须重新租借所有权
1 | fn main() { |
一般的租借引用是不允许修改数据内容的,除非原先的数据是mut的,引用时也是mut引用,才允许修改
1 | let mut s1 = String::from("hello"); |
此时s2允许修改s1的内容
可变引用和不可变引用除了权限不同以外,可变引用不允许多重引用(多个变量引用同一个值),但是不可变引用是允许的
这样做主要是为了避免同时有多个使用者可以进行写操作
垂悬引用
这个其实就是对应着空指针的概念(已经释放资源的指针也算),例如
1 | fn main() { |
s是在函数里申请的,函数结束自动释放,但是返回了s的引用,被main函数接收到了,这里就相当于得到了一个空指针