> For the complete documentation index, see [llms.txt](https://chapin666.gitbook.io/rust-crash-course-zh/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://chapin666.gitbook.io/rust-crash-course-zh/2.-suo-you-quan-ji-chu/su-cheng-ban-di-2-ke.md).

# 速成课第2节

可以说 Rust 与其他流行编程语言最大的区别在于它的所有权模型。 在本课中，我们将接触 Rust 的所有权基础知识。 你可以在 Rust Book 中学习到[更多关于所有权的章节](https://doc.rust-lang.org/book/second-edition/ch04-01-what-is-ownership.html)。

这篇文章是基于 [FP](https://www.fpcomplete.com/rust) 完成 Rust 教学系列的一部分。 如果你在博客之外阅读这篇文章，你可以在[介绍文章的顶部](https://www.snoyman.com/blog/2018/10/introducing-rust-crash-course)找到这个系列中所有文章的链接。 也可[订阅 RSS](https://www.snoyman.com/feed/rust-crash-course) 频道。

## 格式

我将尝试一些课程格式。 我想涵盖以下两个方面：

* 关于所有权更多理论讨论
* 尝试实际编写程序

随着时间的推移，我打算花更多的时间在后者上，而不是前者，尽管我们现在仍然需要花大量的时间在前者上。 我将在这篇文章的开头讨论所有权，然后我们将实现第一个版本的 “弹跳球“。

如果这种方法对大家有效的话，可能会让大家觉得有点拘谨，反馈也是值得赞赏的。

## 与 Haskell 的比较

我将首先比较 Rust 和 Haskell，因为两种语言都有一个强大的不变性概念。 然而，Haskell 是一种垃圾回收语言。 让我们来看看这两种语言的比较。在 Haskell 中：

* 默认情况下一切都是不可变的
* 您可以使用显式的可变包装器(如`IORef` 或 `MVar`)来标记可变性
* 您可以随意对数据的引用进行共享
* 垃圾回收不确定地释放内存
* 当您需要确定性的资源处理（例如文件句柄）时，需要使用[方括号模式](https://www.snoyman.com/blog/2018/10/raii-better-than-bracket-pattern)或类似的模式

在 Rust 中，数据所有权更为重要：它是允许语言绕过垃圾收集的主要内容。 它还允许数据经常驻留在堆栈上而不是堆上，从而提高性能。 此外，它可以确定地运行，这使它成为处理其他资源(如文件句柄)的好方法。

所有权从以下规则开始：

* Rust 中的每个值都会赋值给一个变量，称为它的所有者
* 一次只能有一个所有者
* 当所有者超出作用域时，该值将被删除。

## 简易示例

考虑下面的代码：

```rust
#[derive(Debug)]
struct Foobar(i32);

fn uses_foobar(foobar: Foobar) {
    println!("I consumed a Foobar: {:?}", foobar);
}

fn main() {
    let x = Foobar(1);
    uses_foobar(x);
}
```

**语法注释**：＃\[...] 是编译器编译指示。 ＃\[derive（...）] 是一个示例，类似于在 Haskell 中对 typeclasses 使用派生。 对于某些 Traits，如果您要求，编译器可以自动提供实现。

**语法注释**：格式字符串中的语法注释 {:?} 表示“使用 Debug Traits显示此内容”

Foobar 是所谓的新类型包装器：它是一种新的数据类型，它以带符号的32位整数（i32）包裹并且具有相同生命周期的表示形式。

在 main  函数中，x 包含 Foobar。 当调用 uses\_foobar 时，会将该 Foobar 的所有权将传递给 uses\_foobar。 在 main 中再次使用该 x 将会是一个错误：

```rust
#[derive(Debug)]
struct Foobar(i32);

fn uses_foobar(foobar: Foobar) {
    println!("I consumed a Foobar: {:?}", foobar);
}

fn main() {
    let x = Foobar(1);
    uses_foobar(x);
    uses_foobar(x);
}
```

结果如下:

```rust
error[E0382]: use of moved value: `x`
  --> foo.rs:11:17
   |
10 |     uses_foobar(x);
   |                 - value moved here
11 |     uses_foobar(x);
   |                 ^ value used here after move
   |
   = note: move occurs because `x` has type `Foobar`, which does not implement the `Copy` trait

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.
```

## 销毁

当一个值超出作用域时，使用它的 Drop trait (类似 typeclass) ，然后释放内存。 我们可以通过为 Foobar 编写一个 Drop 实现来说明这一点：

> 在看到输出之前，猜猜下面这个程序的输出是什么。

```rust
#[derive(Debug)]
struct Foobar(i32);

impl Drop for Foobar {
    fn drop(&mut self) {
        println!("Dropping a Foobar: {:?}", self);
    }
}

fn uses_foobar(foobar: Foobar) {
    println!("I consumed a Foobar: {:?}", foobar);
}

fn main() {
    let x = Foobar(1);
    println!("Before uses_foobar");
    uses_foobar(x);
    println!("After uses_foobar");
}
```

输出：

```rust
Before uses_foobar
I consumed a Foobar: Foobar(1)
Dropping a Foobar: Foobar(1)
After uses_foobar
```

注意，该值在 use\_foobar 之后被删除。 这是因为该值被移动到 use\_foobar 函数中，并且当该函数退出时，将调用 drop。

### 练习1

在标准库中有一个函数 std: : mem: : drop，它会立即删除一个值。

## 作用域

Rust 中的作用域目前是非作用域生命周期，尽管有一个非作用域生命周期(None Lexical Lifetimes，NLL)提案正在被合并。 (在 Stack Overflow 上有一个[关于 NLL 的很好的解释](https://stackoverflow.com/questions/50251487/what-are-non-lexical-lifetimes))。 我们可以证明当前作用域的性质：

```rust
#[derive(Debug)]
struct Foobar(i32);

impl Drop for Foobar {
    fn drop(&mut self) {
        println!("Dropping a Foobar: {:?}", self);
    }
}

fn main() {
    println!("Before x");
    let _x = Foobar(1);
    println!("After x");
    {
        println!("Before y");
        let _y = Foobar(2);
        println!("After y");
    }
    println!("End of main");
}
```

> 语法说明: 对未使用的变量使用 \_。

这个程序的输出是：

```rust
Before x
After x
Before y
After y
Dropping a Foobar: Foobar(2)
End of main
Dropping a Foobar: Foobar(1)
```

删除看似多余的大括号并运行程序。  在查看实际输出之前，猜测输出将是什么。

## Borrows / references

有时候你希望能够在不改变所有权的情况下共享对某个值的引用。很简单：

```rust
#[derive(Debug)]
struct Foobar(i32);

impl Drop for Foobar {
    fn drop(&mut self) {
        println!("Dropping a Foobar: {:?}", self);
    }
}

fn uses_foobar(foobar: &Foobar) {
    println!("I consumed a Foobar: {:?}", foobar);
}

fn main() {
    let x = Foobar(1);
    println!("Before uses_foobar");
    uses_foobar(&x);
    uses_foobar(&x);
    println!("After uses_foobar");
}
```

注意事项：

* uses\_foobar 获取类型 \&Foobar 的值，它是“对 Foobar 的不可变引用“
* &#x20;use\_foobar 内部，我们不需要显式地取消引用 foobar 值，这是由 Rust 自动完成的
* 在 main 中，我们现在可以使用 x 来调用 uses\_foobar 两次
* 为了从值创建一个引用，我们在变量前面使用 &&#x20;

**挑战**： 您认为何时打印 “ Dropping a Foobar:” 消息？

还记得上一节课中提到的一个参数 self 的特殊语法吗。 drop 上的签名看起来与 use\_fobar 完全不同。 当您看到 \&mut self 时，您可以将其视为self: \&mut Self。 现在它看起来更类似于 use\_foobar。

### 练习2

我们也希望能够为 uses\_foobar 使用对象语法。 在 Foobar 类型上创建方法 use\_it，以打印 "I consumed" 的信息。

&#x20;*提示： 你需要在 impl Foobar { ... }里面操作。*

## 多存活引用

我们可以更改 main 函数，以允许对 x 同时存在两个引用。 这个版本还在局部变量上添加了显式类型，而不是依赖于类型推断：

```rust
fn main() {
    let x: Foobar = Foobar(1);
    let y: &Foobar = &x;
    println!("Before uses_foobar");
    uses_foobar(&x);
    uses_foobar(y);
    println!("After uses_foobar");
}
```

这在 Rust 中是允许的，因为：

* 对一个变量的多个只读引用不能导致任何数据竞争
* 值的生存期比对它的引用更长。 换句话说，在这种情况下，x 的寿命至少和 y 一样长。

让我们看看打破这一局面的两种方法。

### 引用未存活的值

还记得之前的 std::mem::drop 吗：

```rust
fn main() {
    let x: Foobar = Foobar(1);
    let y: &Foobar = &x;
    println!("Before uses_foobar");
    uses_foobar(&x);
    std::mem::drop(x);
    uses_foobar(y);
    println!("After uses_foobar");
}
```

这将导致错误消息：

```rust
error[E0505]: cannot move out of `x` because it is borrowed
  --> foo.rs:19:20
   |
16 |     let y: &Foobar = &x;
   |                       - borrow of `x` occurs here
...
19 |     std::mem::drop(x);
   |                    ^ move out of `x` occurs here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0505`.
```

### 引用其他的可变引用

您还可以对值进行可变引用。 为了避免数据竞赛，Rust 不允许以任何其他方式同时引用和访问值。

```rust
fn main() {
    let mut x: Foobar = Foobar(1);
    let y: &mut Foobar = &mut x;
    println!("Before uses_foobar");
    uses_foobar(&x); // will fail!
    uses_foobar(y);
    println!("After uses_foobar");
}
```

注意 y 的类型现在是 \&mut Foobar。 像Haskell一样，Rust在类型级别跟踪可变性。 好极了！

## 挑战

尝试猜测下面代码中的哪些行会触发编译错误：

```rust
#[derive(Debug)]
struct Foobar(i32);

fn main() {
    let x = Foobar(1);

    foo(x);
    foo(x);

    let mut y = Foobar(2);

    bar(&y);
    bar(&y);

    let z = &mut y;
    bar(&y);
    baz(&mut y);
    baz(z);
}

// move
fn foo(_foobar: Foobar) {
}

// read only reference
fn bar(_foobar: &Foobar) {
}

// mutable reference
fn baz(_foobar: &mut Foobar) {
}
```

## 可变引用与可变变量

最开始没有解释 x 之前的 mut：

```rust
fn main() {
    let mut x: Foobar = Foobar(1);
    let y: &mut Foobar = &mut x;
    println!("Before uses_foobar");
    uses_foobar(&x);
    uses_foobar(y);
    println!("After uses_foobar");
}
```

默认情况下，变量是不可变的，因此不允许任何形式的突变。 您不能对不可变变量进行可变引用，因此x必须标记为可变。 这是一种更简单的方法：

```rust
#[derive(Debug)]
struct Foobar(i32);

fn main() {
    let mut x = Foobar(1);

    x.0 = 2; // changes the 0th value inside the product

    println!("{:?}", x);
}
```

如果您删除了 mut，这将失败。

### 转移到可变

这让我很困扰，我想这会让其他的 Haskellers 感到困扰。正如刚才提到的，下面的代码不会编译通过：

```rust
#[derive(Debug)]
struct Foobar(i32);

fn main() {
    let x = Foobar(1);

    x.0 = 2; // changes the 0th value inside the product

    println!("{:?}", x);
}
```

显然你不能改变 x，但是让我们稍微改变一下：

```rust
#[derive(Debug)]
struct Foobar(i32);

fn main() {
    let x = Foobar(1);
    foo(x);
}

fn foo(mut x: Foobar) {

    x.0 = 2; // changes the 0th value inside the product

    println!("{:?}", x);
}
```

在学习 Rust 之前，我会反对这一点: x 是不可变的，因此我们不应该允许将它传递给一个需要可变 x 的函数。 这里的可变性是变量的一个特性，而不是值本身。 当你把 x 移动到 foo 中，main 不再有权限访问 x，也不在乎它是否变异了。 在 foo 中，我们已经明确指出 x 可以变异，所以我们很酷。

这与 Haskell 的观点大相径庭。

## Copy trait

上一次我们讨论这个主题时，使用了数值类型vs字符串。让我们再努力一点，下面的代码是否编译？

```rust
fn uses_i32(i: i32) {
    println!("I consumed an i32: {}", i);
}

fn main() {
    let x = 1;
    uses_i32(x);
    uses_i32(x);
}
```

它不能工作，对吧？x 被移动到 uses\_i32，然后再次使用。不过，它编译得很好！为什么？\
Rust有一个特殊的Trait：Copy。它表示一种非常廉价类型，可以自动按值传递。 这正是i32的情况。 如果需要，可以使用 “克隆” Trait显式地执行此操作：

```rust
#[derive(Debug, Clone)]
struct Foobar(i32);

impl Drop for Foobar {
    fn drop(&mut self) {
        println!("Dropping: {:?}", self);
    }
}

fn uses_foobar(foobar: Foobar) {
    println!("I consumed a Foobar: {:?}", foobar);
}

fn main() {
    let x = Foobar(1);
    uses_foobar(x.clone());
    uses_foobar(x);
}
```

为什么我们不需要在第二个 uses\_foobar 时使用 x.clone()？

### 练习3

更改下面代码，而无需完全修改 main 函数，就可以让它成功地编译和运行。 一些提示：Debug 是可以自动派生的特殊trait，为了获得 Copy 实现，您还需要一个 Clone 实现。

```rust
#[derive(Debug)]
struct Foobar(i32);

fn uses_foobar(foobar: Foobar) {
    println!("I consumed a Foobar: {:?}", foobar);
}

fn main() {
    let x = Foobar(1);
    uses_foobar(x);
    uses_foobar(x);
}
```

## 生命周期

与所有权最相关的术语是生命周期。 每个值都必须拥有，并且在 dropped 之前是一直拥有。 到目前为止，我们研究的所有内容都涉及隐式生命周期。 但是，随着代码变得越来越复杂，我们需要更加明确地说明这些生命周期。 我们将再讨论一次。

## 练习4

添加一个 double 函数的实现，让这段代码编译、运行并输出数字2：

```rust
#[derive(Debug)]
struct Foobar(i32);

fn main() {
    let x: Foobar = Foobar(1);
    let y: Foobar = double(x);
    println!("{}", y.0);
}
```

注意： 要提供函数的返回值，在参数列表后面放置： -> ReturnType。

## 结构体和枚举

我在上面提到过，struct Foobar (i32) 是包裹 i32 的一个新类型。 这实际上是更一般的结构体的特殊情况，其中可以有0个或多个字段，以其数值位置命名。 位置开始编号为0，上帝 Linus  Torvalds 的打算。

这有一些例子：

```rust
struct NoFields; // 可能看起来很奇怪，我们以后可能会讲到这个例子
struct OneField(i32);
struct TwoFields(i32, char);
```

您还可以使用记录语法：

```rust
struct Person {
    name: String,
    age: u32,
}
```

结构被称为产品类型，这意味着它们包含多个值。 Rust还提供枚举，即枚举类型或带标签的联合。 这些是替代方法，您可以在其中选择一个选项。 一个简单的枚举将是：

```rust
enum Color {
    Red,
    Blue,
    Green,
}
```

但是枚举变量也可以取值：

```rust
enum Shape {
    Square(u32), // size of one side
    Rectangle(u32, u32), // width and height
    Circle(u32), // radius
}
```

## Bouncy

别说了，我们开始吧! 我想创建一个弹跳球的模拟。

让我们一起逐步完成创建这样一个游戏的过程。在课程结束时，我将提供完整的 src/main.rs。 但是强烈建议你和我一起在下面的章节中实现这一点。 尽量避免复制粘贴，而是自己键入代码，使 Rust 语法更加舒适。

### 初始化项目

这部分很简单：

```rust
$ cargo new bouncy
```

如果你进入 bouncy 这个目录并运行 cargo run，你会得到如下输出：

```rust
$ cargo run
   Compiling bouncy v0.1.0 (/Users/michael/Desktop/bouncy)
    Finished dev [unoptimized + debuginfo] target(s) in 1.37s
     Running `target/debug/bouncy`
Hello, world!
```

我们今天唯一触及的文件是  src/main.rs，它将包含我们程序的源代码。

### 定义数据结构

要跟踪屏幕上弹跳的球，我们需要了解以下信息：

* 包含球的盒子的宽度
* 包含球的盒子的高度
* 球的 x 和 y 坐标
* 球的垂直方向(向上或向下)
* 球的水平方向(左或右)

我们将定义新的数据类型来跟踪垂直和水平方向，并使用 u32 来跟踪位置。

我们可以将 VertDir 定义为枚举。 这是枚举可以处理的简化版本，因为我们没有给它任何有效负载。 我们稍后会做更复杂的东西。

```rust
enum VertDir {
    Up,
    Down,
}
```

继续并定义一个 HorizDir，它可以跟踪我们向左还是向右移动。 现在，要跟踪一个球，我们需要知道其 x 和 y 位置以及其垂直和水平方向。 这将是一个结构，因为我们要跟踪多个值，而不是（例如枚举）在不同选项之间进行选择。

```rust
struct Ball {
    x: u32,
    y: u32,
    vert_dir: VertDir,
    horiz_dir: HorizDir,
}
```

定义一个 Frame 结构，用于跟踪游戏区域的宽度和高度。 然后用一个 Game struct 把它们联系起来:

```rust
struct Game {
    frame: Frame,
    ball: Ball,
}
```

### 创建一个新的 Game

我们可以在 Game 类型本身上定义一个方法来创建一个新的游戏。 我们将分配一些默认的宽度和高度以及初始的球位置。

```rust
impl Game {
    fn new() -> Game {
        let frame = Frame {
            width: 60,
            height: 30,
        };
        let ball = Ball {
            x: 2,
            y: 4,
            vert_dir: VertDir::Up,
            horiz_dir: HorizDir::Left,
        };
        Game {frame, ball}
    }
}
```

重写此实现以避免使用任何 let 语句。

我们如何使用 VertDir::Up。默认情况下，Up 构造函数不会导入到当前名称空间中。 另外，由于局部变量名称与字段名称相同，因此我们可以使用 frame，ball 而不是 frame: frame, ball：ball 来定义游戏。

### 弹起

让我们实现一个球从墙上反弹的逻辑，写出逻辑：

* 如果 x 值是0，在边框的左边，因此我们应该向右移动。
* 如果 y = 0，向下移动。
* 如果 x 比边框的宽度小1，我们应该向左移动。
* 如果 y 比框架的高度小1，我们应该向上移动。
* 否则，我们应该继续朝着同一个方向前进。

我们需要修改 Ball，并以边框作为参数。 我们将在 Ball 类型上实现这个方法：

```rust
impl Ball {
    fn bounce(&mut self, frame: &Frame) {
        if self.x == 0 {
            self.horiz_dir = HorizDir::Right;
        } else if self.x == frame.width - 1 {
            self.horiz_dir = HorizDir::Left;
        }

        ...
```

自己继续实现这个函数的其余部分。

### 移动

一旦我们通过调用 bounce 知道了球的移动方向，我们就可以将球移动一个位置。 我们将在 impl Ball 中添加这个方法：

```rust
fn mv(&mut self) {
    match self.horiz_dir {
        HorizDir::Left => self.x -= 1,
        HorizDir::Right => self.x += 1,
    }
    ...
}
```

还要实现其中的垂直部分。

### 步骤

我们需要向 Game 添加方法来执行游戏的步骤。 这将涉及的弹跳和移动包含在 impl Game 中：

```rust
fn step(&self) {
    self.ball.bounce(self.frame);
    self.ball.mv();
}
```

在这个实现中有一些 bug 需要修复。

### 渲染

我们需要能够显示游戏的完整状态。我们会看到这个初始实现有它的缺陷,，但是我们要打印整个网格。 我们将添加边框，使用字母 o 表示球，并在边框内的所有其他区域放置空格。 为此，我们将使用 Display trait。

让我们把一些类型放到我们的名称空间中，在源文件的顶部添加：

```rust
use std::fmt::{Display, Formatter};
```

现在，让我们确保类型签名是正确的：

```rust
impl Display for Game {
    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
        unimplemented!()
    }
}
```

在实现函数之前，可以使用 unimplemented!() 宏对函数进行打桩。 最后，让我们完成 main 函数，该函数将打印初始游戏：

```rust
fn main () {
    println!("{}", Game::new());
}
```

如果一切都设置正确，运行 cargo run 将导致 “not yet implemented” 的 panic。 如果您遇到编译错误，现在就去修复它。

### 顶端边框

好了，现在我们可以实现fmt了。 首先，让我们绘制顶部边框。 边框的组成将是一个加号，一系列破折号（基于框架的宽度），一个加号和换行符。 我们将使用 write! 宏、 range语法 (low. . high) 和 for 循环：

```rust
write!(fmt, "+");
for _ in 0..self.frame.width {
    write!(fmt, "-");
}
write!(fmt, "+\n");
```

看起来不错，但是我们得到一个编译错误：

```rust
error[E0308]: mismatched types
  --> src/main.rs:79:60
   |
79 |       fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
   |  ____________________________________________________________^
80 | |         write!(fmt, "+");
81 | |         for _ in 0..self.frame.width {
82 | |             write!(fmt, "-");
83 | |         }
84 | |         write!(fmt, "+\n");
   | |                           - help: consider removing this semicolon
85 | |     }
   | |_____^ expected enum `std::result::Result`, found ()
   |
   = note: expected type `std::result::Result<(), std::fmt::Error>`
              found type `()`
```

上面写着 “considering removing this semicolon”。 记住，放置分号将迫使我们的语句返回 () ，但我们需要一个Result值。 而且好像 write!  宏为我们提供了一个Result值。 果然，如果我们删除结尾的分号，我们将得到一些有用的东西：

```rust
    Finished dev [unoptimized + debuginfo] target(s) in 0.55s
     Running `target/debug/bouncy`
+------------------------------------------------------------+
```

您可能会问：来自其他调用 write! 的所有 Result 值呢？ 好问题！ 我们稍后再讨论。

### 底端边框

顶部和底部边框完全相同。 我们不要复制代码，而是定义一个可以调用两次的闭包。 我们用语法 | args | { body }在 Rust 中引入一个闭包。 这个闭包不带参数，所以看起来是这样的：

```rust
let top_bottom = || {
    write!(fmt, "+");
    for _ in 0..self.frame.width {
        write!(fmt, "-");
    }
    write!(fmt, "+\n");
};

top_bottom();
top_bottom();
```

首先，我们将再次得到一个关于 Result 和()的错误。 你需要删除两个分号来解决这个问题。 一旦您完成了这些操作，您将得到一个全新的错误消息。 耶！

```rust
error[E0596]: cannot borrow `top_bottom` as mutable, as it is not declared as mutable
  --> src/main.rs:88:9
   |
80 |         let top_bottom = || {
   |             ---------- help: consider changing this to be mutable: `mut top_bottom`
...
88 |         top_bottom();
   |         ^^^^^^^^^^ cannot borrow as mutable
```

错误消息告诉我们确切的操作：在 let top\_bottom 中间插入一个 mut。 这样做，并确保它可以解决问题。 现在的问题是：为什么？ top\_bottom闭包已从环境中捕获了 fmt 变量。 为了使用它，我们需要调用 write！ 宏，该变量会更改该 fmt 变量。 因此，每次对 top\_bottom 的调用本身都是一个突变。 因此，我们需要将 top\_bottom 标记为可变的。

有三种不同类型的封闭性trait:  Fn、 FnOnce 和 FnMut。 我们将在后面的教程中讨论它们之间的区别。

无论如何，我们现在应该有一个顶部和底部边界在我们的输出。

### 行数

让我们打印每一行。 在两次 top\_bottom() 调用之间，我们将保留一个for循环：

```rust
for row in 0..self.frame.height {
}
```

在这个循环中，我们需要添加左边框和右边框：

```rust
write!(fmt, "|");
// more code will go here
write!(fmt, "|\n");
```

继续使用 cargo run 运行程序，你会有一个不愉快的惊喜：

```rust
error[E0501]: cannot borrow `*fmt` as mutable because previous closure requires unique access
  --> src/main.rs:91:20
   |
80 |         let mut top_bottom = || {
   |                              -- closure construction occurs here
81 |             write!(fmt, "+");
   |                    --- first borrow occurs due to use of `fmt` in closure
...
91 |             write!(fmt, "|");
   |                    ^^^ borrow occurs here
...
96 |         top_bottom()
   |         ---------- first borrow used here, in later iteration of loop
```

哦，不，我们将不得不处理借用检查！

### 与借用检查员斗争

好吧，还记得 top\_bottom 闭包捕获了对 fmt 的可变引用吗？ 好吧，这现在给我们带来了麻烦。 一次只能有一个可变引用在起作用，并且top\_bottom 在我们方法的整个主体中都将其保留。 在这种情况下，有一个简单的解决方法：将fmt用作闭包的参数，而不是捕获它：

```rust
let top_bottom = |fmt: &mut Formatter| {
```

修复 top \_ bottom 的调用，您应该得到如下输出(删除了一些额外的行)。

```
+------------------------------------------------------------+
||
||
||
||
...
+------------------------------------------------------------+
```

好了，现在我们可以回到…

### 列数

还记得 // more code will go here 注释吗？ 是时候换掉它了！ 我们将为每个列使用另一个 for 循环：

```rust
for column in 0..self.frame.width {
    write!(fmt, " ");
}
```

运行 cargo run 会显示一个完整的框架，不错！ 不幸的是，它不包括我们的球。 当列与球的 x 相同时，我们需要写一个 o 字符，而不是空格，对于 y 也是一样的：

```rust
let c = if row == self.ball.y {
    'o'
} else {
    ' '
};
write!(fmt, "{}", c);
```

输出有些问题(cargo run完成测试)。修复它，你的渲染功能将完成！

### 无限循环

我们几乎完成了! 我们还需要在主函数中添加一个无限循环：

* 打印游戏
* 游戏步骤
* 休眠一会儿

我们的目标是 30FPS，所以我们要睡眠 33 毫秒。 但是在 Rust 中我们如何入睡呢？ 为了解决这个问题，让我们去[ Rust standard Library](https://doc.rust-lang.org/std/index.html) 文档中[搜索 sleep](https://doc.rust-lang.org/std/index.html?search=sleep)。 第一个结果是 [std::thread::sleep](https://doc.rust-lang.org/std/thread/fn.sleep.html)，这似乎是个不错的选择。 查看那里的文档，特别是那个很棒的例子，来理解这段代码。

```rust
fn main () {
    let game = Game::new();
    let sleep_duration = std::time::Duration::from_millis(33);
    loop {
        println!("{}", game);
        game.step();
        std::thread::sleep(sleep_duration);
    }
}
```

这段代码中有一个编译错误。 试着预测它是什么。 如果你不能解决它，问编译器，然后修复它。 运行 cargo run  应该会成功显示你一个弹跳球。

### 问题

在这个实现中，我关心两个问题：

* 输出可能有点抖动，特别是在慢速终端上。 我们实际上应该使用类似 curses 库的东西来处理输出的双缓冲。
* 如果您之前运行过 cargo run，则可能看不到。 运行cargo clean 和 cargo build 以强制进行重建，您应该看到以下警告：

```rust
warning: unused `std::result::Result` which must be used
  --> src/main.rs:88:9
   |
88 |         top_bottom(fmt);
   |         ^^^^^^^^^^^^^^^^
   |
   = note: this `Result` may be an `Err` variant, which should be handled
```

我在上面提到过这个问题: 我们忽略了来自调用 write! 宏带来的失败！，但是使用分号丢弃 Result。 这个问题有一个很好的单字符解决方案。 这为正确处理 Rust 中的错误提供了基础。 不过，我们会把这个留到下次再说。 现在，我们只需要忽略这个警告。

### 完整源码

您可以在  Github 上找到这个实现的[完整源代码](https://gist.github.com/snoyberg/5307d493750d7b48c1c5281961bc31d0)。 提醒: 如果您逐步完成上面的代码并自己实现它，那将会更好。

在 top-bottom 调用的最后，我添加了一个我们在教程中还没有涉及到的语法。 下一节我们将更详细地讨论这个问题。
