> 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/1.-shi-yi-shi/su-cheng-ban-di-1-ke.md).

# 速成课第1节

在本课中，我们只需要掌握一些基础知识： 工具、编译能力、基本语法等等。 让我们从工具开始，您可以在下载东西的同时继续阅读。

这篇文章是基于 [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) 频道。

## 工具

Rust 的引导需要是 rustup 工具，它将安装和管理 Rust 工具链。我之所以用复数形式，是因为它既可以管理 Rust 编译器的多个版本，也可以管理替代目标的交叉编译器。 现在，我们将做一些简单的事情。

* [Installation page on rust-lang.org](https://www.rust-lang.org/en-US/install.html)
* [rustup.rs page](https://rustup.rs/)

这两页都会告诉你做同样的事情:

* 在类 Unix 系统运行 `curl https://sh.rustup.rs -sSf | sh`
* 或者运行一个 Windows Installer，可能是 [64-bit installer 64位安装程序](https://win.rustup.rs/x86_64)

请阅读 rust-lang 页面上关于设置 PATH 环境变量的说明。 对于类 unix 系统，你需要在 PATH 中添加 \~/.cargo/bin。

除了 rustup 可执行文件之外，您还将获得：

* `cargo`,  Rust 构建工具
* `rustc`,  Rust 编译器

## Hello, world!

好的，这部分很简单：`cargo new hello && cd hello && cargo run`

我们现在还没有学习所有关于 Cargo 的知识，但是需要掌握一些基本知识：

* `Cargo.toml`包含项目的元数据，包括依赖项
* `Cargo.lock`cargo自身生成
* `src`源文件，目前只包含`src/main.rs`
* `target`包含生成的文件

我们稍后将讨论源代码本身，首先是一些工具注释。

### 使用 rustc 构建

对于这么简单的东西，你不需要 Cargo 来完成。 相反，您可以只使用:  `rustc src/main.rs && ./main`。 如果你喜欢用这种方式进行代码实验，那就去做吧。 但是通常情况下，最好使用 `cargo new` 创建一个临时项目并在其中进行实验。 完全由你决定。

### 运行测试

我们还不会在代码中添加任何测试，但是您可以使用  `cargo test` 在代码中运行测试。

### 额外的工具

两个有用的实用程序是 rustfmt 工具(用于自动格式化代码)和 clippy (用于获取代码建议)。 请注意 clippy 仍然是一个正在进行的工作，有时会给出错误的肯定。 为了让它们构建起来，需要运行：

```bash
$ rustup component add clippy-preview rustfmt-preview
```

然后你可以使用以下命令运行它们:

```bash
$ cargo fmt
$ cargo clippy
```

### IDE

有一些 IDE 支持给那些想要它的人。我听说过有关 IntelliJ IDEA 的 Rust 插件很棒的事情。 就个人而言，我还没有使用过，但首先我也不是一个IDE用户。 此速成班将不使用任何IDE，仅提供基本的文本编辑器支持。

### Macros

好了，我们终于可以查看 src/main.rs 中的源代码了:

```rust
fn main() {
    println!("Hello, world!");
}
```

很简单。 **fn** 表示正在编写一个函数。 名字是 **main**。 它不带参数，也没有返回值。 （或者，更准确地说，它返回单元类型， 类似于 C/C++ 中void，但实际上更接近 Haskell 中的单元类型）。字符串文字看起来非常普通，函数调用与其他 c 样式的语言几乎完全相同。

这是第一个“速成班”部分：为什么 println 后面有个叹号？ 我说“速成班”是因为当我第一次学习 Rust 的时候，我没有看到这方面的解释，这让我困扰了一段时间。

Println 不是一个函数。 这是一个宏（macro）。 这是因为它采用一个格式字符串，需要在编译时检查它。 要证明这一点，请尝试将字符串更改为包含 {} 的字符串。 你会得到一个错误消息，大致如下:

```rust
error: 1 positional argument in format string, but no arguments were given
```

这可以通过提供一个填充占位符参数来解决：

```rust
println!("Hello , world! {} {} {}", 5, true, "foobar");
```

猜猜输出结果是什么，你可能是对的。 但是这给我们留下了一个问题: println 是怎么做到的！ 宏知道如何显示这些不同类型？

## Traits 和 Display

更多的速成课时间！ 为了更好地了解 Display 的工作原理，让我们触发一个编译时错误。 为此，我们将定义一个新的数据类型 Person，创建该类型的值，并尝试打印它:

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

fn main() {
    let alice = Person {
        name: String::from("Alice"),
        age: 30,
    };
    println!("Person: {}", alice);
}
```

稍后我们将介绍更多关于定义自己的结构和枚举的例子，但是如果您感到好奇，可以阅读[《Rust book》相关章节](https://doc.rust-lang.org/book/2018-edition/ch05-00-structs.html)。

如果你尝试编译它，你会得到:

```rust
error[E0277]: `Person` doesn't implement `std::fmt::Display`
  --> src/main.rs:11:28
   |
11 |     println!("Person: {}", alice);
   |                            ^^^^^ `Person` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `Person`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   = note: required by `std::fmt::Display::fmt`
```

这有点罗嗦，但重要的是没有为 **Person** 实现 **`std::fmt::Display`**&#x54;raits。 在 Rust 中，Traits 类似于 Java 中的接口，或者更好的类似于 Haskell 的 typeclass。 (是否注意到某种类似于 Haskell 概念的模式？ 是的，我也这样认为。)

定义自己的特征，并在以后学习实现这些特征，我们将获得很多乐趣。 但是我们现在正面临崩溃。 因此，让我们在此处添加该特征的实现：

```rust
impl Display for Person {
}
```

但这没有奏效:

```rust
error[E0405]: cannot find trait `Display` in this scope
 --> src/main.rs:6:6
  |
6 | impl Display for Person {
  |      ^^^^^^^ not found in this scope
help: possible candidates are found in other modules, you can import them into scope
  |
1 | use core::fmt::Display;
  |
1 | use std::fmt::Display;
  |

error: aborting due to previous error

For more information about this error, try `rustc --explain E0405`.
error: Could not compile `foo`.
```

我们没有将 Display 导入到本地名称空间中。 编译器很有帮助地推荐了两个我们可能需要的不同特性，并告诉我们可以使用 use 语句将它们导入到本地名称空间中。 我们在前面的错误消息中看到，我们希望 std::fmt::Display，因此将 use std::fmt::Display 添加到 src/main.rs 会修复这个错误消息。但是只是为了证明这一点，不需要使用 use 语句！ 我们可以改为：

```rust
impl std::fmt::Display for Person {
}
```

好极了，我们之前的错误消息已经被其他东西取代了：

```rust
error[E0046]: not all trait items implemented, missing: `fmt`
 --> src/main.rs:6:1
  |
6 | impl std::fmt::Display for Person {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `fmt` in implementation
  |
  = note: `fmt` from trait: `fn(&Self, &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error>`
```

我们正在迅速达到“踢轮胎”课程要涵盖的内容的极限。但是希望这能帮助我们为下一次播下种子。

错误消息告诉我们需要在 Display trait 的实现中包含 fmt 方法， 也告诉我们这个的类型签名是什么。 让我们来看看这个签名，或者至少看看错误消息是怎么说的:

```rust
fn(&Self, &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error>
```

这里有很多东西要整理。 我将对每一部分都应用术语，您有可能不会完全理解这些。

* `Self` 是得到实现的事物的类型。 在这种情况下，就是那个 `Person`。
* 在开头添加 `＆` 使其成为对值的引用，而不是值本身。 C++ 开发人员已经习惯了该概念。 许多其他语言也谈论通过引用传递。 在 Rust 中，这与所有权有很大关系。 在 Rust 中，所有权是一个非常重要的话题，我们现在不再讨论。
* `&mut` 是可变的引用。 默认情况下，Rust 中的所有内容都是不可变的，您必须明确地说出是可变的。 稍后我们将探讨为什么引用的可变性对于 Rust 的所有权至关重要。
* 无论如何，第二个参数是对 Formatter 的可变引用。 Formatter 之后的 `<'_>` 是什么？ 这是生命周期参数。 这也与所有权有关。 我们以后也会谈到。
* `->` 表示我们提供函数的返回类型。
* &#x20;Result 是一个枚举，它是一个类型或带标记的联合。 两个类型参数是泛型：成功时的值 和 错误时的值。
* 在成功的情况下，我们的函数返回一个 () 或 单元值。 另一种说法: “如果事情进展顺利，我不会返回任何有用的值”。 在出现错误的情况下，我们返回 std::fmt::Error。
* Rust 没有运行时异常。 相反，当出现问题时，您显式地返回它。 几乎所有的代码都使用 Result 类型来跟踪出错的情况。 这比基于异常的语言更加明确。 但是，像 C 语言这样的语言，很容易忘记检查返回类型以确定是否成功，或者进行错误处理的冗长乏味，Rust 使这个过程不那么痛苦。 我们待会再处理。
  * 注意： Rust确实有 panic 的概念，实际上它的行为类似于运行时异常。 但是，有两个重要区别。 首先，按照惯例，代码不应使用 panic 机制来发信号通知正常错误情况（如未找到文件），而应为完全意外的故障（如逻辑错误）保留panic。 其次，panic（大部分）是无法恢复的，这意味着它们会破坏当前线程。
  * 该文档的先前版本表示，紧急情况是无法恢复的，并且它们破坏了整个线程。然而，正如 j Haigh 指出的那样，这并不完全正确：[catch unwind](https://doc.rust-lang.org/std/panic/fn.catch_unwind.html) 函数通常允许您在不丢失当前线程的情况下捕获并从恐慌中恢复。 我不打算在这里谈论更多细节。

太棒了，这种类型签名本身就为我们提供了足够的素材，可供我们继续学习约5课！ 不用担心，您将能够在不了解所有这些细节的情况下编写一些Rust代码，正如我们在本课程的其余部分中将演示的那样。 但是，如果您真的很喜欢冒险，请随时浏览Rust书以获取更多信息。

## 分号

回到我们的代码，实现我们的 fmt 方法：

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

impl std::fmt::Display for Person {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        write!(fmt, "{} ({} years old)", self.name, self.age)
    }
}

fn main() {
    let alice = Person {
        name: String::from("Alice"),
        age: 30,
    };
    println!("Person: {}", alice);
}
```

我们现在使用 write! 宏，将内容写入提供给我们的 Formatter 方法中。 这超出了我们的讨论范围，但是与生成一堆中间字符串相比，这允许更高效地构造值和生成I/O。

该方法的 \&self 参数是一种特殊的说法，即“这是一个在这个对象上工作的方法”。 这与您在 Python 中编写代码的方式非常相似，尽管在 Rust 中，您必须处理按值传递与按引用传递。

第二个参数名为 fmt，\&mut Formatter 是它的类型。

非常细心的人可能已经注意到，上面提到的错误消息是 Self。 但是，在我们的实现中，我们使用了 \&self。 区别在于 \&Self 是指值的类型，而小写的 \&self 是值本身。 实际上，\&self 参数语法是 self: \&Self 的语法糖。

有人注意到缺少的东西吗？ 您可能会认为我打错了字。 写完后分号在哪里？ 好吧，首先，复制该代码并运行它，以向自己证明这不是拼写错误，并且该代码有效。 现在添加分号，然后尝试再次编译。 你会得到这样的东西：

```rust
error[E0308]: mismatched types
 --> src/main.rs:7:81
  |
7 |       fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
  |  _________________________________________________________________________________^
8 | |         write!(fmt, "{} ({} years old)", self.name, self.age);
  | |                                                              - help: consider removing this semicolon
9 | |     }
  | |_____^ expected enum `std::result::Result`, found ()
  |
  = note: expected type `std::result::Result<(), std::fmt::Error>`
             found type `()`
```

这可能会在 Rust 中造成巨大的混乱。 让我指出您可能已经注意到的事情，如果您是 C / C ++ / Java 背景：我们的方法有一个返回值，但我们从未使用过return！

第二个问题的答案很简单:  Rust 中函数生成的最后一个值作为它的返回值。 这与 Ruby 和 Haskell 非常相似。 只有提前终止才需要返回。

但是我们仍然留下了第一个问题: 为什么这里我们不需要一个分号，为什么加上分号破坏了我们的代码？ Rust 中的分号用于终止语句。 类似于我们之前看到的 use 语句，或者我们在这里简要演示的 let 语句， 它们的值总是 unit 或 ()。 这就是为什么当我们添加分号时，错误信息显示 found type‘()’ 。 去掉分号，表达式本身就是返回值，这就是我们想要的。

Rust 是一种面向表达式的语言，这种东西就是它所指的。 你可以在常见问题解答中看到这一点。就个人而言，我发现这样的分号用法可能很微妙，当我的 C/C++/Java 习惯逐渐出现时，我有时仍然会本能地尝试使用分号。但是幸运的是，编译器可以很快地识别出它们。

## 数字类型

在最后一个概念开始之前我们放入一些代码，我们将从使用数字开始。使用数字有一个很好的理由: 它们是复制值，编译器自动为我们克隆值。请记住，Rust 的很大一部分是所有权，并跟踪谁拥有不平凡的东西。但是，对于基本数值类型，复制这些值非常便宜，编译器会自动为您完成。 这就是我在介绍文章中提到的一些自动魔法。

为了演示，让我们来看看一些适用于数值类型的代码：

```rust
fn main() {
    let val: i32 = 42;
    printer(val);
    printer(val);
}

fn printer(val: i32) {
    println!("The value is: {}", val);
}
```

我们使用 let 语句创建了一个新变量 val。 我们已经明确指出它的类型是 i32，或者是一个32 位带符号整数。 通常，Rust 不需要这些类型注释，因为它通常能够推断类型。 尝试在这里省略类型注释。 不管怎样，我们在 val 上调用函数打印机两次。 一切正常。

现在，让我们用一个字符串来代替。 String 是一个堆分配的值，可以通过 String::from 从字符串文字创建。 (稍后将详细介绍许多字符串类型)。 复制 String 代价很高，所以编译器不会自动为我们做这件事。 因此，这段代码不能编译：

```rust
fn main() {
    let val: String = String::from("Hello, World!");
    printer(val);
    printer(val);
}

fn printer(val: String) {
    println!("The value is: {}", val);
}
```

你会得到这个吓人的错误消息：

```rust
error[E0382]: use of moved value: `val`
 --> src/main.rs:4:13
  |
3 |     printer(val);
  |             --- value moved here
4 |     printer(val);
  |             ^^^ value used here after move
  |
  = note: move occurs because `val` has type `std::string::String`, which does not implement the `Copy` trait

error: aborting due to previous error
```

**练习1**

有两种简单的方法可以修复这个错误消息:&#x20;

* 一种是使用 String 的 clone () 方法
* 另一种是将 printer 更改为获取对 String 的引用&#x20;

实施这两种解决方案（解决方案将在练习解答单独公布）。

### 打印数字&#x20;

我们将用三种不同的循环方式来结束这一课，以打印数字1到10。 我让读者猜猜哪一种方法最合乎习惯。

#### loop 语句

`loop` 创建了一个无限循环。

```rust
fn main() {
    let i = 1;

    loop {
        println!("i == {}", i);
        if i >= 10 {
            break;
        } else {
            i += 1;
        }
    }
}
```

**练习2：**

这段代码不太管用。 不要问编译器就应该试着找出原因。 如果找不到问题，试着编译它。 然后修改代码。 如果您想知道：您可以等效地使用 return 或 return() 退出循环，因为循环的末尾也是函数的末尾。

#### while 语句

这类似于 c 样式的 while 循环: 它需要一个条件来检查。

```rust
fn main() {
    let i = 1;

    while i <= 10 {
        println!("i == {}", i);
        i += 1;
    }
}
```

这与前面的例子有相同的错误。

#### for 循环

For 循环允许您对集合中的每个值执行一些操作。 集合是使用迭代器惰性地生成的，这是 Rust 语言中内置的一个很好的概念。 迭代器与 Python 中的生成器有些类似。

```rust
fn main() {
    for i in 1..11 {
        println!("i == {}", i);
    }
}
```

**练习3： 额外的分号**

你能在上面的例子中省略分号吗？ 与其把代码猛地扔进编译器，不如好好想想什么时候可以扔掉分号，什么时候不可以扔掉分号。

**练习4：Fizzbuzz**

在 Rust 中实现 Fizzbuzz。规则如下:

1. 打印数字1到100
2. 如果数字是3的倍数，输出 fizz 而不是数字
3. 如果数字是5的倍数，输出 buzz 而不是数字
4. 如果数字是3和5的倍数，输出fizzbuzz而不是数字

## 下一节

下一次，我们计划深入了解更多关于所有权的细节，本系列中的计划颇具延展性。 敬请期待。
