rust哪里油桶多_讲透RUST内存模型 一

2ea1392e749aa3ea58eefe7c23ec1fff.png

什么是所有权?

Rust的主要特征是所有权。尽管该功能易于解释,但对其余语言有深远的影响。

所有程序必须在运行时管理它们使用计算机内存的方式。某些语言具有垃圾回收功能,该垃圾回收功能会在程序运行时不断寻找不再使用的内存。在其他语言中,程序员必须显式分配和释放内存。Rust使用第三种方法:通过所有权系统管理内存,该系统具有一组在编译时检查的规则。程序运行时,所有所有权功能都不会减慢其运行速度。

因为所有权是许多程序员的新概念,所以它确实需要一些时间来习惯。好消息是,您对Rust和所有权系统的规则越有经验,就越能自然开发安全有效的代码。继续吧!

了解所有权后,您将拥有坚实的基础,可以理解使Rust独树一帜的功能。在本章中,您将通过研究一些关注非常常见的数据结构的示例(字符串)来学习所有权。

堆栈和堆

在许多编程语言中,您不必经常考虑堆栈和堆。但是在像Rust这样的系统编程语言中,值是在堆栈上还是在堆上对语言的行为以及为什么必须做出某些决定具有更大的影响。所有权的各个部分将在本章后面的堆栈和堆中进行介绍,因此这里是准备工作的简要说明。

堆栈和堆都是代码可在运行时使用的内存部分,但是它们的结构不同。堆栈按获取值的顺序存储值,并以相反的顺序删除值。这称为后进先出。想想一堆盘子:添加更多的盘子时,将它们放在堆的顶部,而当需要一块盘子时,则从顶部取下一个盘子。从中间或底部添加或删除板都无法正常工作!添加数据称为 压入堆栈,而删除数据称为弹出堆栈

堆栈中存储的所有数据必须具有已知的固定大小。相反,在编译时大小未知或大小可能更改的数据必须存储在堆中。堆的组织性较差:将数据放在堆上时,您需要一定数量的空间。内存分配器在堆中找到一个足够大的空白点,将其标记为正在使用中,并返回一个 指针(即该位置的地址)。此过程称为 在堆上分配,有时缩写为allocating。将值压入堆栈不被视为分配。由于指针是已知的固定大小,因此可以将指针存储在堆栈上,但是当需要实际数据时,必须遵循指针。

想想坐在餐厅里。当您输入时,说明组中的人数,工作人员会找到一个适合所有人的空表,并带您到那里。如果您小组中的某人迟到,他们可以询问您在哪里坐下来找到您。

推送到堆栈比在堆上分配要快,因为分配器无需搜索存储新数据的位置。该位置始终位于堆栈的顶部。相比较而言,在堆上分配空间需要更多的工作,因为分配器必须首先找到足够大的空间来容纳数据,然后执行簿记以准备下一次分配。

访问堆中的数据比访问堆栈中的数据要慢,因为您必须遵循指针才能到达那里。如果现代处理器在内存中的跳动较少,则速度会更快。继续类推,考虑一家餐厅的服务器从许多桌子上接订单。在移至下一张桌子之前,将所有订单放在一张桌子上是最有效的。从表A取一个订单,然后从表B取一个订单,再从A取一个订单,再从B取一个订单,这将是一个非常慢的过程。同样,如果处理器处理与其他数据接近(如在堆栈上)而不是更远(如可能在堆上)的数据,则处理器可以做得更好。在堆上分配大量空间也可能需要时间。

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

所有权要解决的所有问题是,跟踪代码的哪些部分正在使用堆上的哪些数据,最大程度地减少堆上的重复数据量以及清理堆上的未使用数据,以免耗尽空间。一旦了解了所有权,您就不必经常考虑堆栈和堆了,但是知道管理堆数据就是所有权存在的原因,可以帮助您解释为什么它按其工作方式运行。

所有权规则

首先,让我们看一下所有权规则。在通过示例进行说明时,请牢记以下规则:

  • Rust中的每个值都有一个变量,称为其所有者
  • 一次只能有一个所有者。
  • 当所有者超出范围时,该值将被删除。

可变范围

我们已经在第2章中介绍了Rust程序的示例。现在,我们已经讲解了基本语法,我们不会fn main() {在示例中包含所有代码,因此,如果您要继续学习,则必须输入以下示例在main函数内部手动执行。结果,我们的示例将更加简洁,让我们专注于实际的细节而不是样板代码。

作为所有权的第一个示例,我们将研究一些变量的范围。范围是程序中项目有效的范围。假设我们有一个看起来像这样的变量:

let s = "hello";

变量s是指字符串文字,其中字符串的值被硬编码到程序的文本中。从声明变量到当前范围的结尾,变量一直有效。清单4-1带有注释,说明变量在何处s有效。

    {                      // s is not valid here, it’s not yet declared        let s = "hello";   // s is valid from this point forward        // do stuff with s    }                      // this scope is now over, and s is no longer valid

代码清单4-1:变量及其有效范围

换句话说,这里有两个重要的时间点:

  • 当s涉及范围时,它是有效的。
  • 它保持有效,直到超出范围

此时,作用域之间以及变量何时有效之间的关系类似于其他编程语言中的关系。现在,我们将通过介绍String类型来在这种理解的基础上。

String类型

为了说明所有权规则,我们需要一个比 第3章“数据类型”部分介绍的数据类型更复杂的数据类型。先前介绍的类型都存储在堆栈中,并在其作用域弹出时从堆栈中弹出。结束了,但是我们想查看存储在堆上的数据,并探索Rust如何知道何时清理该数据。

我们将String在此处作为示例,重点介绍String 与所有权有关的部分。这些方面也适用于其他复杂数据类型,无论它们是由标准库提供还是由您创建。我们将String在第8章中进行更深入的讨论。

我们已经看到了字符串文字,其中字符串值被硬编码到我们的程序中。字符串文字很方便,但是它们并不适合我们可能要使用文本的每种情况。原因之一是它们是不可变的。另一个是在编写代码时并非每个字符串值都可以知道:例如,如果我们想接受用户输入并存储它,该怎么办?对于这些情况,Rust具有第二个字符串类型String。这种类型在堆上分配,因此能够存储在编译时我们不知道的大量文本。您可以String使用from函数从字符串文字创建一个,如下所示:

let s = String::from("hello");

双冒号(::)是一个运算符,它使我们可以from在String类型下命名这个特定函数的名称空间,而不是使用诸如这样的名称string_from。我们将在第5章的“方法语法”部分中以及在第7章的“在模块树中引用项目的路径”中讨论模块的命名空间时,将详细讨论这种语法。

这种字符串可以被改变:

    let mut s = String::from("hello");    s.push_str(", world!"); // push_str() appends a literal to a String    println!("{}", s); // This will print `hello, world!`

那么,这里有什么区别?为什么可以String突变但文字不能突变?区别在于这两种类型如何处理内存。

内存和分配

对于字符串文字,我们在编译时就知道内容,因此文本直接硬编码到最终的可执行文件中。这就是为什么字符串文字快速高效的原因。但是这些属性仅来自字符串文字的不变性。不幸的是,对于在编译时大小未知并且运行程序时大小可能会改变的每段文本,我们无法在二进制文件中放入一滴内存。

对于这种String类型,为了支持可变的,可增长的文本,我们需要在堆上分配一个在编译时未知的内存量来容纳内容。这表示:

  • 必须在运行时从内存分配器请求内存。
  • 完成对的处理后,我们需要一种将该内存返回给分配器的方法String。

第一部分由我们完成:当我们调用时String::from,其实现会请求所需的内存。这在编程语言中几乎是通用的。

但是,第二部分是不同的。在具有垃圾收集器(GC)的语言中,GC会跟踪并清理不再使用的内存,因此我们无需考虑它。如果没有GC,我们有责任确定何时不再使用内存,并像调用请求一样调用代码以显式返回内存。从历史上看,正确执行此操作一直是编程难题。如果我们忘记了,我们将浪费内存。如果我们做得太早,我们将有一个无效变量。如果我们做两次,那也是一个错误。我们需要将正一allocate与正一配对free。

Rust采取了另一条路径:拥有内存的变量超出范围后,内存将自动返回。这是清单4-1中范围示例的一个版本,使用a String代替字符串文字:

    {        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需要的内存返回给分配器:s超出范围时。当变量超出范围时,Rust为我们调用一个特殊函数。该函数称为drop,它的作者String可以在其中放置代码以返回内存。Rust会drop在右花括号处自动调用。

注意:在C++中,这种在项目生命周期结束时释放资源的模式有时称为Resource Acquisition Is Initialization(RAII)。drop如果您使用过RAII模式,Rust中的功能将为您所熟悉。

这种模式对Rust代码的编写方式有深远的影响。现在看来似乎很简单,但是当我们想让多个变量使用我们在堆上分配的数据时,在更复杂的情况下代码的行为可能出乎意料。现在让我们探讨其中的一些情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值