Onwership in Rust

2021/11/26

什么是所有权?

Rust的核心特性就是所有权。

所有程序在运行时都必须管理他们使用计算机内存的方式。通常的语言存在以下两种情况。

Rust语言采取了第三种方式,内存通过一个所有权系统来管理,其中包含一组编译器在编译时检查的规则。当程序运行时,所有权特性不会减慢程序的运行速度(因为Rust把内存管理相关的工作提前到编译时)。

Stack (栈内存) vs Heap (堆内存)

在像Rust这样的系统级编程语言里,一个值在stack上还是heap上对语言的行为和你为什么要做某些决定时存在更大影响的。

存储数据

把值push到stack上不叫分配,因为指针ptr是已知固定大小的,可以把指针存放在stack上。如果你想访问实际的数据,你必须使用指针来定位

The size of a pointer is not fixed. It depends upon different issues like operating system, CPU architecture etc. Usually it depends upon the word size of underlying processor for example a 32 bit computer the pointer size can be 4 bytes. For a 64bit computer the pointer size can be 8bytes. So for a specific architecture pointer size will be fixed.

把数据压入到stack上要比heap上分配快得多, 因为操作系统不需要寻找用来存储新数据的空间,那个位置永远都在stack的顶端,在heap上分配空间需要做更多的工作,操作系统首先需要找到一个足够大的空间来存放数据,然后做好记录方便下次分配

访问数据

访问heap中的数据要比访问stack中的数据慢,因为需要通过指针才能找到heap中的数据,对于现代的处理来说,因为存在缓存的缘故,如果指令在内存中跳转的次数越少,那么速度越快。

函数调用

当你的代码调用函数时,值被传入到函数(也包括指向heap的指针)。函数本地的变量会被压入到stack上。当函数结束后,这些值会从stack上弹出

所有权存在原因

所有权规则

变量作用域

Scope就是程序中一个项目的有效范围,例如

fn main() {
  // s 不可用
  let s = "Hello";// s可用
                  // 可以对s进行相关操作
}// s作用域到此结束,s不再可用

以复杂的String类型讲所有权

String类型(存储在堆上的)比基础标量数据类型更复杂,来看看Rust是如何处理它的

创建String类型的值

可以使用from函数从字符串字面值创建出String类型,例如let s = String::from("Hello");

fn main() {
  let mut s = String::from("Hello"); // mut(即mutable)代表 s是可以修改的
  
  s.push_str(", World");
  
  println!("{}", s);
}

内存和分配

Rust采用了不同的方式:对于某个值来说,当拥有它的变量走出作用范围时,Rust会调用一个特殊的函数drop,内存会立即自动交还给操作系统。

变量和数据交互的方式: 移动(Move)

一个String由三部分组成:

fn main() {
  let s1 = String::from("Hello");
  let s2 = s1;
  prinln!("{}", s1); // 会报错, borrow of moved value: 's1',
  									// move occurs because `s1` has type 'std::string::String', which does not implement the 'Copy' trait
}

为了保证内存的安全,在上面的代码中,当s1移动到s2后,Rust会让s1失效。

Rust中一个设计原则: Rust不会自动创建数据的深拷贝。

变量和数据交互的方式: 克隆(Clone)

如果真想对heap上面的String数据进行深度拷贝,而不仅仅是stack上的数据,可以使用clone方法。

fn main() {
  let s1 = String::from("Hello");
  let s2 = s1.clone();
  
  println!("{}, {}", s1, s2);
}

克隆方法相当于是将stack和heap上的数据都完整拷贝了一份,clone这种方法是比较消耗资源的。

Stack上的数据: 复制

fn main() {
  let x = 5;
  let y = x;
  println!("{}, {}", x, y); // x是整型,为标量类型,在编译的时就确定了大小
}

Copy trait,可以用于像整数这样完全存放在stack上面的类型,如果一个类型实现了Copy这个trait,那么旧的变量在赋值后仍然可用,如果一个类型或者该类型的一部分实现了Drop trait,那么Rust不允许让它再去实现Copy trait了

一些拥有Copy Trait的类型


Comments🎉