0
点赞
收藏
分享

微信扫一扫

【Rust所有权机制】Rust所有权机制详细解析与应用实战

Rust所有权

1、认识所有权

所有权(系统)是 Rust 最独特的功能,其令 Rust 无需垃圾回收(garbage collector)即可保障内存安全。

因此,理解Rust 中所有权如何工作是十分重要的。本文我们将讲到所有权以及相关功能:借用、slice 以及 Rust 如何在内存中布局数据。


1.1 什么是所有权

Rust 的核心功能(之一)是 所有权(ownership)。虽然这个功能说明起来很直观,不过它对语言的其余部分有着更深层的含义。

所有程序都必须管理其运行时使用计算机内存的方式。

一些语言中使用垃圾回收GC在程序运行过程中来时刻寻找不再被使用的内存,但是stw(stop the world)对性能的伤害极大;

在另一些语言中,程序员必须亲自分配和释放内存,比如C、C++。

Rust 则选择了第三种方式:内存被一个所有权系统管理,它拥有一系列的规则使编译器在编译时进行检查。任何所有权系统的功能都不会导致运行时开销。

因为所有权对很多程序员来说都是一个新概念,需要一些时间来适应。

好消息是随着你对 Rust 和所有权系统的规则越来越有经验,你就越能自然地编写出安全和高效的代码。持之以恒!

当你理解了所有权系统,你就会对这个使 Rust 如此独特的功能有一个坚实的基础。


总结:Rust 中的每一个值都有一个 所有者(owner)。值在任一时刻有且只有一个所有者。当所有者(变量)离开作用域,这个值将被丢弃。

Rust通过所有权机制来管理内存,编译器在编译时就会根据所有权规则对内存使用进行检查。


Rust内存管理模型:

所谓内存管理,就是对内存的分配和释放

Rust采用Ownership rules、semantics、Borrow Checker、Lifetime等在编译时期做这些检查,在编译期,如果发现内存有问题的话,直接不让编译通过

而且通过所有权机制,来限制内存错误的产生,直接将错误扼杀在摇篮之中,这一套整体的就是所有权机制。

Rust只能无限接近C/C++的性能,并不能超越。因为很多底层的还是C/C++


术语介绍:

STW(Stop the world)

“Stop the world"是与垃圾回收(Garbage Collection)相关的术语,它指的是在进行垃圾回收时系统暂停程序的运行。

这个术语主要用于描述一种全局性的暂停,即所有应用线程都被停止,以便垃圾回收器能够安全地进行工作。

这种全局性的停止会导致一些潜在的问题,特别是对于需要低延迟和高性能的应用程序。

需要注意的是,并非所有的垃圾回收算法都需要"stop the world”,有一些现代的垃圾回收器采用了一些技术来减小全局停顿的影响,比如并发垃圾回收和增量垃圾回收。

但是,编程语言只要使用了GC,就没办法和没有GC的语言进行性能比较。


1.2 栈(Stack)与堆(Heap)

在很多语言中并不经常需要考虑到栈与堆。不过在像 Rust 这样的系统编程语言中,值是位于栈上还是堆上在更大程度上影响了语言的行为以及为何必须做出这样的选择。

我们会在本文的稍后部分描述所有权与堆与栈相关的部分,所以这里只是一个用来预热的简要解释。

栈和堆都是代码在运行时可供使用的内存部分,不过他们以不同的结构组成。栈以放入值的顺序存储并以相反顺序取出值。这也被称作 后进先出(last in, first out)。


栈:即一个后进先出的模式。增加数据叫进栈, 移出数据叫出栈。栈中所有的数据必须暂用已知且固定大小。

编译的时候,数据的类型和大小是固定的,就分配在栈上

堆:即内存中的一块区域, 编译时大小未知或大小可能变化的数据, 存储在堆上。


栈比堆分配内存要快, 因为入栈时无需为新存储的数据查找合适空间, 因为其位置总是在栈顶。

相反, 堆分配要做更多的工作,要找一个足够的空间,并记录。

访问堆上的数据比访问栈上的数据慢,因为必须通过指针来访问。

当你的代码调用一个函数时,传递给函数的值(包括可能指向堆上数据的指针)和函数的局部变量被压入栈中。当函数结束时,这些值被移出栈。


想象一下一叠盘子:当增加更多盘子时,把他们放在盘子堆的顶部,当需要盘子时,也从顶部拿走。

不能从中间也不能从底部增加或拿走盘子!增加数据叫做 进栈(pushing ontothe stack),而移出数据叫做 出栈(popping off the stack)。

操作栈是非常快的,因为它访问数据的方式:永远也不需要寻找一个位置放入新数据或者取出数据因为这个位置总是在栈顶。

另一个使得栈快速的性质是栈中的所有数据都必须有一个已知且固定的大小。


对于在编译时未知大小或大小可能变化的数据,可以把他们储存在堆上。堆是缺乏组织的:当向堆放入数据时,我们请求一定大小的空间。

操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回给我们一个其位置的 指针(pointer)。

这个过程称作 在堆上分配内存(allocating on the heap),并且有时这个过程就简称为“分配”(allocating)。

向栈中放入数据并不被认为是分配。因为指针是已知的固定大小的,我们可以将指针储存在栈上,不过当需要实际数据时,必须访问指针。

想象一下去餐馆就坐吃饭。当进入时,你说明有几个人,餐馆员工会找到一个够大的空桌子并领你们过去。

如果有人来迟了,他们也可以通过询问来找到你们坐在哪。

访问堆上的数据要比访问栈上的数据要慢因为必须通过指针来访问。现代处理器在内存中跳转越少就越快(缓存)。

继续类比,假设有一个服务员在餐厅里处理多个桌子的点菜。在一个桌子报完所有菜后再移动到下一个桌子是最有效率的。

从桌子 A 听一个菜,接着桌子 B 听一个菜,然后再桌子 A,然后再桌子 B 这样的流程会更加缓慢。

出于同样原因,处理器在处理的数据之间彼此较近的时候(比如在栈上)比较远的时候(比如可能在堆上)能更好的工作。在堆上分配大量的空间也可能消耗时间。

当调用一个函数,传递给函数的值(包括可能指向堆上数据的指针)和函数的局部变量被压入栈中。

当函数结束时,这些值被移出栈。

记录何处的代码在使用堆上的什么数据,最小化堆上的冗余数据的数量以及清理堆上不再使用的数据以致不至于耗尽空间,这些所有的问题正是所有权系统要处理的。

一旦理解了所有权,你就不需要经常考虑栈和堆了,不过理解如何管理堆内存可以帮助我们理解所有权为何存在以及为什么要以这种方式工作。

举报

相关推荐

0 条评论