正式开始
变量在函数调用时发生了什么
main() 函数中定义了一个动态数组 data 和一个值 v,然后将其传递给函数 find_pos,在 data 中查找 v 是否存在,存在则返回 v 在 data 中的下标,不存在返回 None
fn main() {
let data = vec![10, 42, 9, 8];
let v = 42;
if let Some(pos) = find_pos(data, v) {
println!("Found {} at {}", v, pos);
}
}
fn find_pos(data: Vec<u32>, v: u32) -> Option<usize> {
for (pos, item) in data.iter().enumerate() {
if *item == v {
return Some(pos);
}
}
None
}
动态数组因为大小在编译期无法确定,所以放在堆上,并且在栈上有一个包含了长度和容量的胖指针指向堆上的内存
大多数语言多重引用给内存管理带来极大麻烦
大多数语言方案
C/C++
开发者手工处理,容易出现内存泄漏和野指针问题
Java
使用追踪式GC,通过定期扫描堆上数据看还有没有人引用,来管理堆内存
存在STW(Stop The World)的问题
OC/Swift
使用自动引用计数(ARC),在编译时自动添加维护引用计数的代码,引用计数为0时清理内存。
存在性能问题和循环引用问题
Rust解决思路
所有权和 Move 语义
通过制定规则保证一个值最好只有一个拥有者(单一所有权)
- 一个值只能被一个变量所拥有,这个变量被称为所有者
- 一个值同一时刻只能有一个所有者(不能有两个变量拥有相同的值。变量赋值、参数传递、函数返回等行为,旧的所有者会把值的所有权转移给新的所有者,以便保证单一所有者的约束)
- 当所有者离开作用域,其拥有的值被丢弃,内存得到释放
作用域:在 Rust 中,一对花括号括起来的代码区就是一个作用域
举个例子
fn main() {
// 创建了一个不可变数据 data
let data = vec![1, 2, 3, 4];
// 将 data 赋值给 data1
// 赋值之后,data 指向的值被移动给了 data1,它自己便不可访问了
// 这里的解决方式,可以将data.clone()在堆上复制一份传给data1
let data1 = data;
// data1 作为参数被传给函数 sum(),在 main() 函数下,data1 也不可访问了
println!("sum of data1: {}", sum(data1));
// 试图访问 data1 和 data,有两个错误
// value borrowed here after move
println!("data1: {:?}", data1);
// value used here after move
println!("sum of data: {}", sum(data));
}
fn sum(data: Vec<u32>) -> u32 {
data.iter().fold(0, |acc, x| acc + x)
}
解决栈上简单数据的所有权转移问题
- 如果你不希望值的所有权被转移,在 Move 语义外,Rust 提供了 Copy 语义。如果一个数据结构实现了 Copy trait,那么它就会使用 Copy 语义。在你赋值或者传参时,值会自动按位拷贝(浅拷贝)
- 如果你不希望值的所有权被转移,又无法使用 Copy 语义,那你可以借用数据
Rust默认是Move语义,Copy语义可选需要自己实现
Copy 语义和 Copy trait
- 符合 Copy 语义的类型,在你赋值或者传参时,值会自动按位拷贝
- 默认使用Move,如果实现了Copy则使用Copy(当你要移动一个值,如果值的类型实现了 Copy trait,就会自动使用 Copy 语义进行拷贝,否则使用 Move 语义进行移动)
- 可以使用rustc --explain EXXXX 查看具体错误和错误样例
- 原生类型,包括函数、不可变引用和裸指针实现了 Copy;
- 数组和元组,如果其内部的数据结构实现了 Copy,那么它们也实现了 Copy;
- 可变引用没有实现 Copy;
- 非固定大小的数据结构,没有实现 Copy。
可以查看官方Copy trait文档的Implementors
小结
- 所有权:一个值只能被一个变量所拥有,且同一时刻只能有一个所有者,当所有者离开作用域,其拥有的值被丢弃,内存得到释放。
- Move 语义:赋值或者传参会导致值 Move,所有权被转移,一旦所有权转移,之前的变量就不能访问。
- Copy 语义:如果值实现了 Copy trait,那么赋值或传参会使用 Copy 语义,相应的值会被按位拷贝(浅拷贝),产生新的值