You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.8 KiB

4 Ownership

所有权是Rust最独特的特征。使rust能在不需要垃圾收集器的情况下,保证内存安全。所有权的相关特征:借用、切片以及rust如何在内存中布局数据。

什么是所有权?

所有权是rust管理内存的一组规则。所有语言都必须在运行时管理计算机内存。java中使用的是垃圾回收机制,c++使用显式分配和释放内存,而rust是通过所有权定义一组规则,若违法任何规则,则程序无法变异。所有权的任何功能都不会在程序运行时减慢程序的速度。

堆栈:先进后出,分配更快,因为始终在堆栈的顶部分配。

堆:访问速度比堆栈慢,因为需根据指针。

所有权规则:

1. 每个值都有一个owner。
1. 同一时间只能有一个owner。
1. 当owner超出范围时,改值将被删除。

变量及其有效范围:

  1. 当s进入范围时,他是有效的。
  2. 在超出范围之前一直有效。

String 所有权

image-20230103115454713

image-20230103140613746

字符串文字(不带mut)是不可变的,但使用push_str,会改变字符串。

内存和分配

当string不带mut时,在编译时就知道内容,故而文本直接被硬编码到可执行文件中,速度高效。

但大多情况下,String类型都是可变的、可增长的文本片段。为支持可变需做以下工作:

  1. 必须在运行时从内存分配器请求内存。
  2. 当字符串定义完成后,需使用一种方法将内存返回给分配器。

第一部分由我们完成:当调用String::from时,它的实现请求它需要的内存。

第二部分略有不同。在java中,gc会跟踪并清理不再使用的内存。在c++中,开发人员需确定何时不再使用内存,并调用代码显式释放内存(困难重重)。而在rust中,采用不同的方式:一个变量超出范围,内存就会返回。

	{
        let s = String::from("hello"); // s is valid from this point forward

        // do stuff with s
    }                                  // this scope is now over, and s is no
                                       // longer valid

当调用String::from时,我们将String所需的内存返回给分配器。当字符串变量离开其作用域时,rust会自动调用drop函数。创建String类型的开发可以在drop函数中编写代码,将内存返还给分配器、Rust在关闭大括号时自动调用drop函数。

变量交互

简单类型交互

image-20230103150405427

String类型交互

String类型的三部分:指向保存字符串堆的指针,长度和容量。

image-20230103155545065

image-20230103155938348

image-20230103154738909

image-20230103154600735

若不转移所有权,当一个变量超出范围时,rust在清理堆内存时,将同时释放s1和s2相同位置的内存。将会产生双重释放错误,会导致内存损坏,也会导致安全漏洞。

不同于浅拷贝,在rust中此操作会使第一个变量无效,故它被称为移动。此操作中我们会说s1被移动到了s2。

image-20230103160852981

如此如此,就只有s2有效,当超出范围时只有它单独释放内存!

此现象也说明了一个设计选择: Rust永远不会自动创建数据的“深”副本。因此,可以认为任何自动复制在运行时性能方面都是廉价的。

clone

image-20230103163926151

image-20230103164637401

同时复制了堆数据,开销很大。

只在堆栈中的数据

在编译时具有已知大小的整数等类型完全存储在堆栈中,因此可以快速复制实际值。这意味着我们没有理由x在创建变量后阻止其生效y。换句话说,这里的深拷贝和浅拷贝没有区别,所以调用clone和通常的浅拷贝没有什么不同,

let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);

Rust 中有一种特殊的注释,叫做 Copy trait,它可以放在存储在栈中的类型上。如果一个类型实现了 Copy trait,使用它的变量不会移动,而是被简单复制,使它们在赋值给另一个变量后仍然有效。

如果类型或其任何部分实现了 Drop trait,Rust 将不会让我们在类型上添加 Copy 注释。如果类型需要在值超出作用域时执行特殊操作,并且将 Copy 注释添加到该类型,则会出现编译时错误。

作为一般规则,任何简单标量值组都可以实现 Copy,而任何需要分配或为某种资源的类型都不能实现 Copy。以下是一些实现 Copy trait 的类型:

  • 所有整数类型,例如 u32。

  • 布尔类型 bool,其值为 true 和 false。

  • 所有浮点类型

  • 所有字符类型 char。

  • 元组,如果它们只包含也实现了 Copy 的类型。例如,(i32,i32)实现了 Copy,但(i32,String)不能。

    这是实现 Copy trait 的一些类型的完整列表。注意,这些类型在赋值给另一个变量时不会移动,因为它们实现了 Copy trait,而不是因为它们存储在栈中。

例如,像数组这样的类型可以存储在堆上,但也可以实现 Copy trait。这意味着在赋值给另一个变量时,该数组的所有内容将被简单复制,而不是移动。

同样,字符串 slice 也可以实现 Copy trait,尽管它们存储在堆上。这意味着在赋值给另一个变量时,它们将被简单复制,而不是移动。

总的来说,如果希望类型能够被简单复制而不是移动,可以尝试将 Copy trait 添加到该类型上。这在某些情况下可以使代码更简单,并且在运行时间方面也很容易。但是,要注意,如果类型需要在值超出作用域时执行特殊操作,则不能将 Copy trait 添加到该类型。

函数所有权

rust中变了传递给函数的机制与将变量赋值给另一变量类似。

image-20230103171551681

String类型不实现Copy trait,故而函数结束后打印s(使用&s就没有此问题)会出错,但x由于是int类型,故函数结束后仍然存在。

Return Values and Scope

返回值也可以转移所有权。

image-20230103173822709

变量的所有权每次都遵循相同的模式:将一个值赋给另一个变量会移动它。当包含堆上数据的变量超出范围时,drop除非数据的所有权已移至另一个变量,否则将清除该值。

image-20230103174018518

但对于一个本应通用的概念来说,这是太多的仪式和大量的工作。对我们来说幸运的是,Rust 有一个使用值而不转移所有权的特性,称为引用