07_rust的所有权
所有权
所有权是rust最独特、核心的特性,使得rust无GC也可保证内存安全。
其他语言都有在运行时管理自身内存的机制,比如GC,或者程序员手动申请和释放。
rust则采用了第三种方式:所有权
- 内存通过一所有权系统来管理,含一组编译时用于检测的规则。
- 当程序运行时,所有权不会影响程序速度。
stack(栈)和heap(堆)内存
stack按值的接收顺序来存储,按相反的顺序移除(先进先出,LIFO),分别是压栈和弹栈。所有存入stack的数据必须已知固定大小。
heap用于存储编译时大小未知运行时才确定大小、及可能发生变化的数据,需代码中手动申请,然后系统找到一块适合大小的空间并返回指针(这个过程叫分配内存),然后存入数据。
所有权解决的问题:
- 跟踪代码哪些地方在使用heap的哪些数据
- 清理heap上未使用的数据,避免空间不足
- 最小化heap上的重复数据
所有权规则
- 每个值都有一个变量,每个变量是该值的所有者
- 每个值同时只能有一个所有者
- 当所有者超出作用域(scope)时,该值将被删除
以string类型为例了解所有权
String类型定义和试用
fn main() {
let mut s = String::from("Hello"); // 在运行时申请分配相应大小的内存
s.push_str(" world");
println("{}", s);
}
rust采用的回收方式:对于某个值,当拥有它的变量离开作用范围是,内存会立即自动交还给操作系统。会自动调用drop函数。
变量和数据的交互方式:移动(Move)
多个变量可与同一个数据使用move方式来交互,如
fn main() {
let s1 = String::from("Hello");
let s2 = s1;
}
一个String由3部分组成:一个指向存放字符串内容的内存指针,一个长度,一个容量。
比如s1
string s1: # 以下三个字段构成一个String对象,存放在stack上
ptr: 0x1234 # 内存首地址
len: 5 # 存放字符串内容所需的字节数
capacity: 5 # 存放string从操作系统里获得的总字节数
# 以下是heap上开辟的内存
0x1234:
hello
而执行let s2 = s1时,只会复制一份stack的数据,ptr的值不变,指向heap里的同一个地址。
s1 ==> s2
string s2:
ptr: 0x1234 # 也指向heap上的hello
len: 5
capacity: 5
当离开作用域时,rust会自动调用drop函数,并将变量使用的heap内存释放,当s1、s2离开作用域时,都会尝试释放相同的内存。
在c中,这种现象称之为二次释放(double free)错误。
在rust中为了保证内存安全,执行let s2 = s1后,rust会让s1失效,这样在离开s1作用域时不需再释放ptr指向的内存。
如果还继续使用s1则会编译报错:
fn main() {
let s1 = String::from("Hello");
let s2 = s1;
println!("test {}", s1);
}
/*
编译报错
25 | let s1 = String::from("Hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
26 | let s2 = s1;
| -- value moved here
27 | println!("test {}", s1);
| ^^ value borrowed here after move
*/
之前有浅拷贝(shallow copy)和深拷贝(deep copy)的概念,
而rust做的一种移动操作,相当于让s2替代了s1,s1失效。
隐含的一个设计原则:rust不会自动创建数据的深拷贝。
如果想深拷贝,实现heap数据拷贝,则使用克隆(Clone)方法。
fn main() {
let s1 = String::from("Hello");
let s2 = s1.clone();
println!("test {}", s1);
}
Copy trait
stack上的数据:复制,无需考虑深拷贝、浅拷贝的问题,也就没有移动失效的问题。
fn main() {
let x = 5;
let y = x;
println!("test {} {}", x, y);
}
Copy trait:trait可简单理解为接口,可用于像整数这些完全存放在stack上的数据类型,如果一个数据类型实现了Copy trait,则旧的变量在赋值后仍然可用。
如果一个类型或者该类型的一部分实现了Drop trait,则Rust不允许它再实现Copy trait。
一些拥有Copy trait的类型
- 任何简单标量的组合类型都可Copy
- 任何需要分配内存或某种资源的都不可Copy
一些拥有Copy trait的类型:
- 所有的整数类型,如u32
- bool
- char
- 所有浮点类型,如f64
- Tuple(元组,如果其所有字段都是Copy的),如(i32, i32)是,(i32, String)则不是
所有权和函数
-
参数
在语义上,将值传递给函数和把值赋给变量是类似的,将值传递给函数将发生移动或复制。
如果参数类型是String等类型,则Move操作,如果是i32类型,则Copy操作。 -
返回值
在返回值的过程中也同样会发生所有权的转移。
一个变量的所有权总是遵循同样的模式:
- 赋值过程发生Move/Copy。
- 当包含heap数据的变量离开作用域时,值会被drop函数清理,除非数据的所有权移动到了另一个变量上。
如果想保留所有权,则可使用引用的方式。