Skip to content
文档章节

所有权

所有权问题跟数据在内存中存放位置有关。涉及栈和堆的设计。

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

  1. 字符串变量 &str,引用字符串的一部分
rust
{
  let s = String::from("hello, world!");

  let s1 = &s[1..=3];
}
  1. 数组 slice, 引用数组的一部分
rust
{
  let arr = [1,2,3,4];

  let slice = &arr[1...3];
}

slice 只是引用字符换或数组的一部分数据 ,因此 slice 没有数据所有权。