作用: 管理堆数据的存储和访问.
设计原则:
以提升运行时的性能为主;
在进行赋值操作时进行所有权转移;目的: 解决内存安全
规则
- Rust 中每个值都有一个被其称为所有者 owner 的变量
- 值在任何时刻都只有一个所有者
- 当所有者离开作用域时, 值将被丢弃
内存
常见的内存管理方式有三种
-
垃圾回收机制: JS
-
手动分配和释放内存
-
通过所有权系统管理内存
所有权是以编译时的内存安全检查替代运行时的垃圾回收机制。
堆与栈
栈:
- 后进先出, Last In Fast Out.
- 栈中的数据具有已知的固定大小
- 入栈总是在栈顶
堆:
- 缺乏组织, 存储相对混乱
- 需要返回表示该内存地址的指针 pointer
- 需要将指针存储在栈中, 通过指针访问数据
堆中的数据存储和访问, 要比栈中的复杂太多, 需要考虑到位置, 内存空间等一系列问题.
实现
Rust 通过借用检查器(一种静态分析器)实现所有权。
借用检查器是 Rust 编译器的一个组件,它跟踪整个程序中数据的使用位置,并且根据所有权规则,它能够识别需要执行释放的数据位置。
借用检查器可以确保在运行时,已释放的内存永远不会被访问。它甚至消除了数据竞争问题。
rust 面向编译器编程。
变量作用域
变量在离开作用域的时, 会调用 Drop Trait 用于释放内存.
变量与数据的交互
move: 移动后原变量无效
针对堆内存, 会导致所有权转移.
let s1 = String::from("hello");
let s2 = s1;
print!("s1: {}", s1); // 错误, s1 不可访问
需满足规则: 值在任何时刻都只有一个所有者
原理: 如果原变量有效, 就会存在 s1 和 s2 的两个数据指针同时指向了同一位置, 在离开作用域, 会释放两次, 导致二次释放的错误.
**隐藏的设计原则: ** Rust 永远不会自动创建的深拷贝. 一切以提高运行时的性能为主.
clone:
在实际开发中, 如果确实需要深度复制 String 中堆上的数据, 可以使用 clone.
只会复制数据, 不会同步更改.
let s1 = String::from('Hello')'
let s2 = s1.clone();
println!("s1 = {}, s2={}", s1, s2);
copy:
对于栈中的数据, 变量的赋值可以通过拷贝实现, 复制特定的语义.
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
对于用了 Copy Trait 的特殊注解, 都可以实现旧的变量在赋值给其它变量后继续使用.
但是禁止实现了 Drop Trait 的类型使用 Copy Trait.
其它
函数和返回值也可以进行所有权的转移, 在语义上与变量赋值相似.
引用与借用
借用 borrowing 是一种在不获取所有权的情况下访问数据
& 引用 reference 在捕获去所有权的情况下访问数据; 返回的是一个内存地址, 不可能为空. 是一种指针类型。
* 解引用(dereferencing) 是与引用相反的操作; 可以获取指向内存中的数据. 当需要数据的时候就需要解引用。
特殊的作用域
引用的作用域是从声明的位置开始一致持续到最后一次使用位置.
编译器在作用域结束之前判断不再使用引用的能力被称为 非词法作用域生命周期.
// 不可运行
{
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题
println!("{}, {}, and {}", r1, r2, r3);
}
// 可运行
{
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
println!("{} and {}", r1, r2);
// 此位置之后 r1 和 r2 不再使用
let r3 = &mut s; // 没问题
println!("{}", r3);
}
避免数据竞争
当多个引用指向同一个内存位时, 至少有一个在执行写操作, 且它们的动作没有同步时
可变引用: &mut
底层逻辑: 不能在拥有不可变引用的同时拥有可变引用
悬垂引用
指向已分配或已释放内存位置的指针, 及指针无效的情况
解决方案:
- 在任意给定的时间内,要么只能有一个可变引用, 要么只能有多个不可变引用
- 引用必须总是有效的