rust指针视频教程 (rust智能指针使用方法)

使用Box<T>指向数据的堆

rust智能指针使用方法,rust指针编程视频教程

最简单的智能指针是一个框,其类型为书面形式 Box<T>。框允许您将数据存储在堆而不是堆栈上。堆栈上剩下的是指向堆数据的指针。请参考第4章以查看堆栈和堆之间的区别。

除了将数据存储在堆而不是堆栈上之外,存储盒没有性能开销。但是它们也没有很多额外的功能。在以下情况下,您将最常使用它们:

  • 如果您有一个在编译时无法得知其大小的类型,并且想要在需要精确大小的上下文中使用该类型的值
  • 当您拥有大量数据并且想要转移所有权但要确保在执行此操作时不会复制数据
  • 当您想拥有一个值并且只在乎它是实现特定特征的类型而不是特定类型时

我们将在“使用框启用递归类型”部分中演示第一种情况。在第二种情况下,转移大量数据的所有权可能要花费很长时间,因为数据是在堆栈上四处复制的。为了在这种情况下提高性能,我们可以将大量数据存储在一个盒子中的堆中。然后,只有少量的指针数据被复制到堆栈中,而它所引用的数据则保留在堆中的某个位置。第三种情况称为特质对象,第17章专门针对该主题专门论述了“使用允许使用不同类型的值的特性对象”这一节。因此,您在这里学到的内容将在第17章中再次应用!

使用Box<T>将数据存储在堆上

在讨论该用例之前Box<T>,我们将介绍其语法以及如何与存储在中的值进行交互Box<T>。

清单15-1显示了如何使用一个盒子i32在堆上存储一个值:

文件名:src / main.rs

fn main() {
    let b = Box::new(5);
    println!("b = {}", b);
}

清单15-1:i32使用盒子将值存储在堆上

我们定义变量b以使其a的值Box指向value 5,该值在堆上分配。该程序将打印b = 5; 在这种情况下,我们可以像访问堆栈中的数据一样访问框中的数据。就像任何拥有的值一样,当框超出范围时(如b结尾)main,该框将被释放。对该盒(存储在堆栈上)及其指向的数据(存储在堆上)进行释放。

将单个值放在堆上不是很有用,因此您不会经常以这种方式单独使用盒子。在i32大多数情况下,具有默认值存储在堆栈上的单个值之类的值更合适。让我们看一下这样的情况:框允许我们定义如果没有框则不允许的类型。

使用框启用递归类型

在编译时,Rust需要知道一个类型占用多少空间。递归类型是一种其大小在编译时未知的类型,其中一个值本身可以具有另一个相同类型的值。因为值的这种嵌套理论上可以无限地继续,所以Rust不知道递归类型的值需要多少空间。但是,框的大小是已知的,因此,通过在递归类型定义中插入框,您可以拥有递归类型。

让我们探索cons list,它是函数式编程语言中常见的数据类型,作为递归类型的示例。我们将定义的缺点列表类型很简单,除了递归。因此,当您遇到涉及递归类型的更复杂情况时,我们将使用的示例中的概念将非常有用。

有关缺点列表的更多信息

一个利弊清单是来自Lisp程序设计语言及其方言的数据结构。在Lisp中,该cons函数(“ construct function”的缩写)从其两个参数构造一个新的对,它们通常是一个值和另一个对。这些包含对的对构成一个列表。

cons函数概念已进入更通用的函数式编程术语:“将x cons到y ”非正式意味着通过将元素x放在此新容器的开头,然后是容器y来构造新的容器实例。

缺点列表中的每个项目都包含两个元素:当前项目的值和下一个项目。列表中的最后一项仅包含一个值,Nil 而没有下一项。缺点列表是通过递归调用该cons 函数产生的。表示递归基本情况的规范名称为Nil。注意,这与第6章中的“ null”或“ nil”概念不同,后者是无效或不存在的值。

尽管函数式编程语言经常使用缺点列表,但缺点列表不是Rust中常用的数据结构。在大多数情况下,当您在Rust中具有项目列表时,Vec<T>是更好的选择。其他更复杂的递归数据类型在各种情况下也很有用,但是从cons列表开始,我们可以探索如何让框定义递归数据类型而不会造成太多干扰。

清单15-2包含一个cons列表的枚举定义。请注意,由于该List类型的大小未知,因此我们将无法编译该代码。

文件名:src / main.rs

enum List {
    Cons(i32, List),
    Nil,
}

清单15-2:首次尝试定义代表i32值列表的cons列表数据结构的枚举

注意:就i32本示例而言,我们正在实现一个仅包含值的缺点列表。正如我们在第10章中讨论过的,我们可以使用泛型来实现它,以定义一个可以存储任何类型的值的缺点列表类型。

使用List类型存储列表1, 2, 3将类似于清单15-3中的代码:

文件名:src / main.rs

use crate::List::{Cons, Nil};

fn main() {
    let list = Cons(1, Cons(2, Cons(3, Nil)));
}

清单15-3:使用List枚举存储列表1, 2, 3

第一个Cons值成立1,另一个List值成立。该List值是另一个Cons保存的值,也是2另一个List值。该List值是一个Cons包含3的List值,最后是 Nil一个非递归变量,表示列表的结尾。

如果尝试编译清单15-3中的代码,则会得到清单15-4中所示的错误:

$ cargo run
   Compiling cons-list v0.1.0 (file:///projects/cons-list)
error[E0072]: recursive type `List` has infinite size
 --> src/main.rs:1:1
  |
1 | enum List {
  | ^^^^^^^^^ recursive type has infinite size
2 |     Cons(i32, List),
  |               ---- recursive without indirection
  |
  = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to make `List` representable

error[E0391]: cycle detected when processing `List`
 --> src/main.rs:1:1
  |
1 | enum List {
  | ^^^^^^^^^
  |
  = note: ...which again requires processing `List`, completing the cycle
  = note: cycle used when computing dropck types for `Canonical { max_universe: U0, variables: [], value: ParamEnvAnd { param_env: ParamEnv { caller_bounds: [], reveal: UserFacing, def_id: None }, value: List } }`

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0072, E0391.
For more information about an error, try `rustc --explain E0072`.
error: could not compile `cons-list`.

To learn more, run the command again with --verbose.

清单15-4:尝试定义递归枚举时遇到的错误

错误显示此类型“具有无限大小”。原因是我们定义 List了一个递归的变量:它直接拥有自身的另一个值。结果,Rust无法弄清楚存储一个List值需要多少空间 。让我们细分一下为什么会出现此错误。首先,让我们看一下Rust如何决定存储非递归类型的值需要多少空间。

rust智能指针使用方法,rust指针编程视频教程

计算非递归类型的大小

回想一下Message在第6章中讨论枚举定义时在清单6-2中定义的枚举:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

为了确定为一个Message值分配多少空间,Rust会遍历每个变体以查看哪个变体需要最多的空间。Rust看到Message::Quit不需要任何空间,Message::Move需要足够的空间来存储两个i32值,依此类推。因为将仅使用一个变体,所以Message值所需的最大空间就是存储最大变体所需的空间。

将其与Rust尝试确定递归类型(如List清单15-2中的枚举)需要多少空间时发生的情况形成对比。编译器首先查看Cons变量,该变量包含typei32的值和type的值List。因此,Cons需要的空间量等于的大小i32加上的大小List。为了弄清楚该List 类型需要多少内存,编译器会从变体开始查看变Cons 体。所述Cons变体保持类型的值i32和类型的值 List,并且该过程继续无限,如图15-1。

rust智能指针使用方法,rust指针编程视频教程

图15-1:List由无限Cons变体组成的无限

使用Box<T>得到一个递归类型与已知大小

Rust无法确定为递归定义的类型分配多少空间,因此编译器在清单15-4中给出了错误。但是错误确实包含以下有用的建议:

  = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to make `List` representable

在此建议中,“间接”表示不直接存储值,而是通过存储指向该值的指针来更改数据结构以间接存储值。

因为aBox<T>是指针,Rust总是知道需要多少空间Box<T> :指针的大小不会根据其指向的数据量而变化。这意味着我们可以Box<T>在Cons变量内部放置一个变量,而不是List直接放置另一个值。在Box<T>将指向下一个List 值,这将是在堆上,而不是内部Cons的变体。从概念上讲,我们仍然有一个列表,该列表是用列表“保存”其他列表创建的,但是此实现现在更像是将项目彼此相邻放置而不是放在彼此内部。

我们可以将List清单15-2中的枚举定义和List清单15-3中的用法更改为清单15-5中的代码,这些代码将进行编译:

文件名:src / main.rs

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}

清单15-5:为了具有已知大小List使用的定义Box<T>

该Cons变体将需要i32加号的大小加上用于存储框的指针数据的空间。该Nil变种没有存储值,所以它需要用不到的空间Cons变异。现在我们知道,任何List值都将占用i32一个框的指针数据加上一个框的指针数据的大小。通过使用一个框,我们打破了无限的递归链,因此编译器可以确定存储List值所需的大小。图15-2显示了该Cons变型的外观。

rust智能指针使用方法,rust指针编程视频教程

图15-2:List由于Cons持有Box

框仅提供间接和堆分配;它们没有任何其他特殊功能,就像我们将在其他智能指针类型中看到的那样。它们也没有这些特殊功能带来的任何性能开销,因此它们在诸如cons列表之类的情况下非常有用,在这些情况下,间接访问是我们唯一需要的功能。我们还将在第17章中讨论盒子的更多用例。

该Box<T>类型是智能指针,因为它实现了Deref特征,该特征允许将Box<T>值视为引用。当Box<T> 值超出范围时,由于Drop特质实现,框所指向的堆数据也会被清除。让我们更详细地探讨这两个特征。对于我们将在本章其余部分讨论的其他智能指针类型提供的功能,这两个特征将更加重要。