所有权
根据内存管理方式的不同,主流的编程语言可以划分为:
- 安全优先:由垃圾收集器来管理内存,比如Java、Python、Golang
- 控制优先:由开发者负责释放内存,比如C、C++
Rust旨在同时保证安全和性能。Rust以这种方式来解决这个问题:严格限制程序使用指针的方式。
这些规则为Rust实现安全的并发编程奠定了基础。使用Rust的线程原语,保证内存安全的规则可以用于保证避免数据竞争。
所有权
在Rust中,所有权是内置在语言中的,并通过编译期检查确保强制执行。每个值都只有一个决定它生命周期的所有者。当所有者被释放,它拥有的值也会被删掉。
所有者和它们拥有的值组成一棵树。 每个变量都是一棵树;当这个变量离开作用域时,销毁整棵树。
Rust在一下方面扩展了所有权树的想法:
- 值可以从一个所有者移动到另一个所有者。允许修改所有权树
- 简单类型如整数、浮点数和字符被排除在所有权规则以外。它们被称为Copy类型
- 标准库提供了引用计数的指针类型Rc和Arc,它们允许一个值有多个所有者
- 可以借用一个值的引用,引用是生命周期受限的非占有的指针
move
对于move操作,源对象放弃了值的所有权,把所有权转移给了目的对象,同时源 对象变为未初始化的状态;此时目的对象控制值的生命周期。Rust 程序一次一个值、一次一个 move 的构建和拆除复杂的结构。
Rust在几乎所有场景下都使用move操作。
fn test_move() {
struct Person {
name: String,
birth: i32,
}
let mut composers = Vec::new();
composers.push(Person {
name: "test".to_string(),
birth: 22,
});
println!("{}", composers.len())
}
在上述代码中,move 发生的场景如下:
- 从函数返回值
- 构造新的值
- 向函数传值
Copy类型
赋予一个Copy类型的值会拷贝它,而不是移动它。源对象仍然保持初始化状态和可用性,它的值不会发生改变。向函数和构造器传递Copy类型的变量也类似。
标准的 Copy 类型包括所有的机器整数和浮点数类型、char 和 bool 类型,以及少数其他类型。Copy 类型的元组或者固定大小的数组也是 Copy 类型。
只有简单的逐位拷贝的类型才可以是Copy类型。
用户自定义类型只是默认不是 Copy 类型。如果你的结构体的所有字段都是 Copy 类型, 那么你可以通过在定义上方加上属性 #[derive(Copy, Clone)]
来把它变为Copy类型。
在Rust中,所有的移动都是逐字节的浅拷贝,同时把源对象设置为未初始化。
Rust 的原则之一就是开销对程序员来说必须是明显的。基本的操作必须保持简单。
Rc和Arc:共享所有权
Rc 和 Arc 类型非常相似,它们唯一的不同之处在于: Arc(原子引用计数) 可以直接安全的在线程之间共享;Rc 则使用更快一些的非线程安全代码来更新引用计数。如果你不需要在线程之间共享指针,那就没有必要承担 Arc 的性能损失,所以你应该使用 Rc;Rust 会阻止你无意间在线程之间传递 Rc。
对于任意类型T,一个Rc<T>
值是一个指向在堆上分配的T类型值的指针,同时还附有一个引用计数。克隆一个 Rc<T>
类型的值并不意味着拷贝T,它只是简单的创建另一个指向它的指针,并且递增引用计数。
拥有三个引用的引用计数字符串内存分布如图所示:
一个 Rc 指针拥有的值是不可变的。
Rust的内存和线程安全保证依赖于没有值既是共享的又是可变的。Rust 假设 Rc 指针指向的值要被共享,因此它必须是不可变的。
引用
引用绝不应该比它们指向的值生命周期更长。为了强调这一点,Rust将创建某个值的引用称为借用值。
引用可以访问一个值,同时不影响它的所有权。 引用有两种:
- 共享引用:只能读取不能修改被引用的值。共享引用是Copy类型
- 可变引用:可以读取和修改被引用的值。可变引用不是Copy类型
将共享引用和可变引用完全分离是内存安全的基础。
以值传参数;以引用传参数
使用引用
引用允许函数在不获取所有权的情况下访问或者操作一个数据结构。
#[test]
fn test_reference() {
let x = 10;
let r = &x;
assert_eq!(*r, 10);
let mut y = 2;
// 创建可变引用
let m = &mut y;
*m += 30;
assert_eq!(*m, 32);
}
引用可以进行比较,注意比较操作符的类型必须完全相同
引用永不为空。
Rust有两种类型的胖指针:
- 切片的引用是一种胖指针,包括切片的起始地址和它的长度。
- trait 对象,一个实现了特定 trait 的值的引用。一个 trait 对象包含值的地址和一个指向该值对 trait 的实现的指针,用于调用 trait 的方法。
引用安全
一个生命周期是一个引用可以安全使用的程序区间:可能是一条语句、一个表达式、也可能是一些变量的作用域,或者类似的区间。生命周期完全是 Rust 的编译期视图。在运行时引用只是一个地址,生命周期是它的类型的一部分,并没有运行时表示。
引用的两种约束:
- 变量的生命周期必须包含或包括它的引用的生命周期。
- 如果你把引用存储到变量 r 中,引用的生命周期必须覆盖变量的整个生命周期:从它初始化到最后一次使用它。
第一种约束限制了引用生命周期的上限,第二张约束限制了它的下限。
如下图所示,这样的生命周期是不存在的,违反了引用的两种约束。
在 Rust 中,一个函数的签名总是暴露出函数体的行为。
函数签名中的生命周期让 Rust 能获得传进函数的引用和函数返回的引用的关系,然后保证它们都被安全地使用。
当一个引用类型出现在其他类型的定义中时,你必须写出它的生命周期:
struct S {
r: &'static i32
}
总结
移动和引用计数指针是两种缓解所有权树过于死板的方法。