深色模式
所有权
所有权问题跟数据在内存中存放位置有关。涉及栈和堆的设计。
Rust 变量离开作用域后需要对变量进行释放,如果两个变量指向同一个内存,如果同时离开,则需要释放两次,出现 bug。Rust 采用所有权转移,交付所有权后,原先变量不再使用。
栈和堆内存
栈和堆是怎样的,在数据结构中有介绍,此处不再赘述。
在 Rust 中,将基本类型数据存放在栈中,原因是其大小内存大小固定,读取速度快,会被硬编码到程序中。
基本类型数据通过复制(copy)来传递数据。
rust
{
let s1 = 'hello, liuzunkun';
let s2 = s1;
// s1, s2 同时存在
}
// s1, s2, 释放
而对于可变容量类型的数据,比如 String, 则其容量在编译时不能确定,因此需要将其在堆上分配一块内存,在栈上分配固定大小内存存储指针。
栈上指针结构,s1
- ptr: 指针,指向在堆上分配内存地址
- len: String 当前使用了多少长度字节
- capacity: String 从分配器获取了多少长度字节
rust
{
let s1 = String::from('hello, liuzunkun');
// s1 作用域
let s2 = s1; // s1 所有权转到 s2
// s2 拥有权限
}
// s2 释放
如果 let s2=s1;
, 则 s1 将所有权转移(move) 到 s2, s1 和 s2 不再同时存在了。这区别于 js 的浅拷贝和深拷贝的概念, rust 中不存在深拷贝。
当然,也可以堆 s1 的数据实体进行复制,这设计到另外的功能了。
函数参数的复制和所有权转移
同上对于不同类型数据的处理,基本数据类型数据是复制,其他复杂的类型是所有权转移到了函数内部。
rust
{
let s1 = String::from('hello');
print_string(s1);// 所有权转移
// s1 不再有效
let x = 1;
print_num(x); // 此时x复制处理
// x 仍然有效
}
因此为了避免函数参数传递中的所有权转移 bug, 我们常用借用(refering) 来处理。
rust
{
fn print_string(&s) {
...
}
print_string(&s1); // 此时只是对s1 借用,所有权未转移
// s1 仍然有效
}
借用
Rust 中 借用(borrow) 是非常好的设计,这可以避免所有权转移。
同时,rust 对于 同一个数据,某一时刻(场景)要么只能存在一个可变引用,要么可以存在多个不可变引用。
rust
{
let mut s1 = String::from("hello, liuzunkun.");
let s2 = s1; // 所有权转移
let s3 = &s2; // s3 引用 s2, 不可变
let s4 = &s2; // s3, s4 引用 s2, 不可变
let s5 = &mut s2; // s5 可变引用 s2
// 此后,只能有一个 s5 进行可变引用,不能有 let s6 = &mut s2;
// 同时,不能存在 可变引用和不可变引用同时存在的情况
}
特别注意,引用容易出现悬垂引用的额情况。
slice
- 字符串变量 &str,引用字符串的一部分
rust
{
let s = String::from("hello, world!");
let s1 = &s[1..=3];
}
- 数组 slice, 引用数组的一部分
rust
{
let arr = [1,2,3,4];
let slice = &arr[1...3];
}
slice 只是引用字符换或数组的一部分数据 ,因此 slice 没有数据所有权。