From df75e68925f789d01ae579be5bb5ad5eabcda97c Mon Sep 17 00:00:00 2001 From: yang yue Date: Sat, 25 Feb 2017 23:47:33 +0800 Subject: [PATCH] wip --- docs/ch01-00-introduction.html | 2 +- docs/ch01-01-installation.html | 2 +- docs/ch01-02-hello-world.html | 2 +- docs/ch02-00-guessing-game-tutorial.html | 2 +- docs/ch03-00-common-programming-concepts.html | 2 +- docs/ch03-01-variables-and-mutability.html | 2 +- docs/ch03-02-data-types.html | 2 +- docs/ch03-03-how-functions-work.html | 2 +- docs/ch03-04-comments.html | 2 +- docs/ch03-05-control-flow.html | 2 +- docs/ch04-00-understanding-ownership.html | 2 +- docs/ch04-01-what-is-ownership.html | 2 +- docs/ch04-02-references-and-borrowing.html | 2 +- docs/ch04-03-slices.html | 2 +- docs/ch05-00-structs.html | 2 +- docs/ch05-01-method-syntax.html | 2 +- docs/ch06-00-enums.html | 2 +- docs/ch06-01-defining-an-enum.html | 2 +- docs/ch06-02-match.html | 2 +- docs/ch06-03-if-let.html | 2 +- docs/ch07-00-modules.html | 2 +- docs/ch07-01-mod-and-the-filesystem.html | 2 +- ...07-02-controlling-visibility-with-pub.html | 2 +- docs/ch07-03-importing-names-with-use.html | 2 +- docs/ch08-00-common-collections.html | 2 +- docs/ch08-01-vectors.html | 2 +- docs/ch08-02-strings.html | 2 +- docs/ch08-03-hash-maps.html | 10 +- docs/ch09-00-error-handling.html | 125 ++++ ...09-01-unrecoverable-errors-with-panic.html | 205 ++++++ ...h09-02-recoverable-errors-with-result.html | 347 ++++++++++ docs/ch09-03-to-panic-or-not-to-panic.html | 202 ++++++ docs/ch10-00-generics.html | 233 +++++++ docs/ch10-01-syntax.html | 173 +++++ docs/ch10-02-traits.html | 116 ++++ docs/ch10-03-lifetime-syntax.html | 108 ++++ docs/index.html | 2 +- docs/print.html | 591 +++++++++++++++++- src/SUMMARY.md | 12 +- src/ch09-00-error-handling.md | 11 + ...ch09-01-unrecoverable-errors-with-panic.md | 121 ++++ src/ch09-02-recoverable-errors-with-result.md | 327 ++++++++++ src/ch09-03-to-panic-or-not-to-panic.md | 122 ++++ src/ch10-00-generics.md | 151 +++++ src/ch10-01-syntax.md | 68 ++ src/ch10-02-traits.md | 0 src/ch10-03-lifetime-syntax.md | 0 47 files changed, 2947 insertions(+), 31 deletions(-) create mode 100644 docs/ch09-00-error-handling.html create mode 100644 docs/ch09-01-unrecoverable-errors-with-panic.html create mode 100644 docs/ch09-02-recoverable-errors-with-result.html create mode 100644 docs/ch09-03-to-panic-or-not-to-panic.html create mode 100644 docs/ch10-00-generics.html create mode 100644 docs/ch10-01-syntax.html create mode 100644 docs/ch10-02-traits.html create mode 100644 docs/ch10-03-lifetime-syntax.html create mode 100644 src/ch09-00-error-handling.md create mode 100644 src/ch09-01-unrecoverable-errors-with-panic.md create mode 100644 src/ch09-02-recoverable-errors-with-result.md create mode 100644 src/ch09-03-to-panic-or-not-to-panic.md create mode 100644 src/ch10-00-generics.md create mode 100644 src/ch10-01-syntax.md create mode 100644 src/ch10-02-traits.md create mode 100644 src/ch10-03-lifetime-syntax.md diff --git a/docs/ch01-00-introduction.html b/docs/ch01-00-introduction.html index 8b8b49e..ab04602 100644 --- a/docs/ch01-00-introduction.html +++ b/docs/ch01-00-introduction.html @@ -47,7 +47,7 @@
diff --git a/docs/ch01-01-installation.html b/docs/ch01-01-installation.html index a7a4c3c..bc5ffe3 100644 --- a/docs/ch01-01-installation.html +++ b/docs/ch01-01-installation.html @@ -47,7 +47,7 @@
diff --git a/docs/ch01-02-hello-world.html b/docs/ch01-02-hello-world.html index 234d9b8..2e0c98e 100644 --- a/docs/ch01-02-hello-world.html +++ b/docs/ch01-02-hello-world.html @@ -47,7 +47,7 @@
diff --git a/docs/ch02-00-guessing-game-tutorial.html b/docs/ch02-00-guessing-game-tutorial.html index 6e4b961..d292ac5 100644 --- a/docs/ch02-00-guessing-game-tutorial.html +++ b/docs/ch02-00-guessing-game-tutorial.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-00-common-programming-concepts.html b/docs/ch03-00-common-programming-concepts.html index df22a20..da1ed2f 100644 --- a/docs/ch03-00-common-programming-concepts.html +++ b/docs/ch03-00-common-programming-concepts.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-01-variables-and-mutability.html b/docs/ch03-01-variables-and-mutability.html index 7cd211e..a40aef2 100644 --- a/docs/ch03-01-variables-and-mutability.html +++ b/docs/ch03-01-variables-and-mutability.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-02-data-types.html b/docs/ch03-02-data-types.html index c8b5df9..21d32bb 100644 --- a/docs/ch03-02-data-types.html +++ b/docs/ch03-02-data-types.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-03-how-functions-work.html b/docs/ch03-03-how-functions-work.html index 18f6355..21b256f 100644 --- a/docs/ch03-03-how-functions-work.html +++ b/docs/ch03-03-how-functions-work.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-04-comments.html b/docs/ch03-04-comments.html index aad56ff..66eaac5 100644 --- a/docs/ch03-04-comments.html +++ b/docs/ch03-04-comments.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-05-control-flow.html b/docs/ch03-05-control-flow.html index d755b7c..b4a0a71 100644 --- a/docs/ch03-05-control-flow.html +++ b/docs/ch03-05-control-flow.html @@ -47,7 +47,7 @@
diff --git a/docs/ch04-00-understanding-ownership.html b/docs/ch04-00-understanding-ownership.html index 40d79f6..b987844 100644 --- a/docs/ch04-00-understanding-ownership.html +++ b/docs/ch04-00-understanding-ownership.html @@ -47,7 +47,7 @@
diff --git a/docs/ch04-01-what-is-ownership.html b/docs/ch04-01-what-is-ownership.html index 8c2f688..80fd0df 100644 --- a/docs/ch04-01-what-is-ownership.html +++ b/docs/ch04-01-what-is-ownership.html @@ -47,7 +47,7 @@
diff --git a/docs/ch04-02-references-and-borrowing.html b/docs/ch04-02-references-and-borrowing.html index 22e72c5..c9d8e6f 100644 --- a/docs/ch04-02-references-and-borrowing.html +++ b/docs/ch04-02-references-and-borrowing.html @@ -47,7 +47,7 @@
diff --git a/docs/ch04-03-slices.html b/docs/ch04-03-slices.html index 5426622..f3ae5d2 100644 --- a/docs/ch04-03-slices.html +++ b/docs/ch04-03-slices.html @@ -47,7 +47,7 @@
diff --git a/docs/ch05-00-structs.html b/docs/ch05-00-structs.html index 7f6ca26..ba9230a 100644 --- a/docs/ch05-00-structs.html +++ b/docs/ch05-00-structs.html @@ -47,7 +47,7 @@
diff --git a/docs/ch05-01-method-syntax.html b/docs/ch05-01-method-syntax.html index a829bcf..a20e718 100644 --- a/docs/ch05-01-method-syntax.html +++ b/docs/ch05-01-method-syntax.html @@ -47,7 +47,7 @@
diff --git a/docs/ch06-00-enums.html b/docs/ch06-00-enums.html index b08e5a7..c044d4e 100644 --- a/docs/ch06-00-enums.html +++ b/docs/ch06-00-enums.html @@ -47,7 +47,7 @@
diff --git a/docs/ch06-01-defining-an-enum.html b/docs/ch06-01-defining-an-enum.html index 931db5c..623462c 100644 --- a/docs/ch06-01-defining-an-enum.html +++ b/docs/ch06-01-defining-an-enum.html @@ -47,7 +47,7 @@
diff --git a/docs/ch06-02-match.html b/docs/ch06-02-match.html index 3650771..24eac41 100644 --- a/docs/ch06-02-match.html +++ b/docs/ch06-02-match.html @@ -47,7 +47,7 @@
diff --git a/docs/ch06-03-if-let.html b/docs/ch06-03-if-let.html index d3c72b7..9f94762 100644 --- a/docs/ch06-03-if-let.html +++ b/docs/ch06-03-if-let.html @@ -47,7 +47,7 @@
diff --git a/docs/ch07-00-modules.html b/docs/ch07-00-modules.html index 7c4b0d8..190c05e 100644 --- a/docs/ch07-00-modules.html +++ b/docs/ch07-00-modules.html @@ -47,7 +47,7 @@
diff --git a/docs/ch07-01-mod-and-the-filesystem.html b/docs/ch07-01-mod-and-the-filesystem.html index ab25694..8326222 100644 --- a/docs/ch07-01-mod-and-the-filesystem.html +++ b/docs/ch07-01-mod-and-the-filesystem.html @@ -47,7 +47,7 @@
diff --git a/docs/ch07-02-controlling-visibility-with-pub.html b/docs/ch07-02-controlling-visibility-with-pub.html index 4e5b3d6..77dab5d 100644 --- a/docs/ch07-02-controlling-visibility-with-pub.html +++ b/docs/ch07-02-controlling-visibility-with-pub.html @@ -47,7 +47,7 @@
diff --git a/docs/ch07-03-importing-names-with-use.html b/docs/ch07-03-importing-names-with-use.html index 522badb..f024941 100644 --- a/docs/ch07-03-importing-names-with-use.html +++ b/docs/ch07-03-importing-names-with-use.html @@ -47,7 +47,7 @@
diff --git a/docs/ch08-00-common-collections.html b/docs/ch08-00-common-collections.html index bba4cce..e22c77c 100644 --- a/docs/ch08-00-common-collections.html +++ b/docs/ch08-00-common-collections.html @@ -47,7 +47,7 @@
diff --git a/docs/ch08-01-vectors.html b/docs/ch08-01-vectors.html index a54583a..704c84a 100644 --- a/docs/ch08-01-vectors.html +++ b/docs/ch08-01-vectors.html @@ -47,7 +47,7 @@
diff --git a/docs/ch08-02-strings.html b/docs/ch08-02-strings.html index e461c77..2f6eba0 100644 --- a/docs/ch08-02-strings.html +++ b/docs/ch08-02-strings.html @@ -47,7 +47,7 @@
diff --git a/docs/ch08-03-hash-maps.html b/docs/ch08-03-hash-maps.html index 34d50c6..e6d342b 100644 --- a/docs/ch08-03-hash-maps.html +++ b/docs/ch08-03-hash-maps.html @@ -47,7 +47,7 @@
@@ -204,6 +204,10 @@ println!("{:?}", map); + +
@@ -214,6 +218,10 @@ println!("{:?}", map); + +
diff --git a/docs/ch09-00-error-handling.html b/docs/ch09-00-error-handling.html new file mode 100644 index 0000000..b87fd61 --- /dev/null +++ b/docs/ch09-00-error-handling.html @@ -0,0 +1,125 @@ + + + + + Rust 程序设计语言 中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

错误处理

+
+

ch09-00-error-handling.md +
+commit fc825966fabaa408067eb2df3aa45e4fa6644fb6

+
+

Rust 对可靠性的执着也扩展到了错误处理。错误对于软件来说是不可避免的,所以 Rust 有很多功能来处理当现错误的情况。在很多情况下,Rust 要求你承认出错的可能性可能性并在编译代码之前就采取行动。通过确保不会只有在将代码部署到生产环境之后才会发现错误来使得程序更可靠。

+

Rust 将错误组合成两个主要类别:可恢复错误recoverable)和不可恢复错误unrecoverable)。可恢复错误通常代表向用户报告错误和重试操作是合理的情况,比如未找到文件。不可恢复错误通常是 bug 的同义词,比如尝试访问超过数组结尾的位置。

+

大部分语言并不区分这两类错误,并采用类似异常这样方式统一处理他们。Rust 并没有异常。相反,对于可恢复错误有Result<T, E>值和panic!,它在遇到不可恢复错误时停止程序执行。这一章会首先介绍panic!调用,接着会讲到如何返回Result<T, E>。最后,我们会讨论当决定是尝试从错误中恢复还是停止执行时需要顾及的权衡考虑。

+ +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch09-01-unrecoverable-errors-with-panic.html b/docs/ch09-01-unrecoverable-errors-with-panic.html new file mode 100644 index 0000000..b2f2c21 --- /dev/null +++ b/docs/ch09-01-unrecoverable-errors-with-panic.html @@ -0,0 +1,205 @@ + + + + + Rust 程序设计语言 中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

panic!与不可恢复的错误

+
+

ch09-01-unrecoverable-errors-with-panic.md +
+commit 380e6ee57c251f5ffa8df4c58b3949405448d914

+
+

突然有一天,糟糕的事情发生了,而你对此束手无策。对于这种情况,Rust 有`panic!宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,并接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。

+
+

Panic 中的栈展开与终止

+

当出现panic!时,程序默认会开始展开unwinding),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接终止abort),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,可以由 panic 时展开切换为终止,通过在 Cargo.toml[profile]部分增加panic = 'abort'。例如,如果你想要在发布模式中 panic 时直接终止:

+
[profile.release]
+panic = 'abort'
+
+
+

让我们在一个简单的程序中调用panic!

+

Filename: src/main.rs

+
fn main() {
+    panic!("crash and burn");
+}
+
+

运行程序将会出现类似这样的输出:

+
$ cargo run
+   Compiling panic v0.1.0 (file:///projects/panic)
+    Finished debug [unoptimized + debuginfo] target(s) in 0.25 secs
+     Running `target/debug/panic`
+thread 'main' panicked at 'crash and burn', src/main.rs:2
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
+
+

最后三行包含panic!造成的错误信息。第一行显示了 panic 提供的信息并指明了源码中 panic 出现的位置:src/main.rs:2 表明这是 src/main.rs 文件的第二行。

+

在这个例子中,被指明的那一行是我们代码的一部分,而且查看这一行的话就会发现panic!宏的调用。换句话说,panic!可能会出现在我们的代码调用的代码中。错误信息报告的文件名和行号可能指向别人代码中的panic!宏调用,而不是我们代码中最终导致panic!的那一行。可以使用panic!被调用的函数的 backtrace 来寻找(我们代码中出问题的地方)。

+

使用panic!backtrace

+

让我们来看看另一个因为我们代码中的 bug 引起的别的库中panic!的例子,而不是直接的宏调用:

+

Filename: src/main.rs

+
fn main() {
+    let v = vec![1, 2, 3];
+
+    v[100];
+}
+
+

这里尝试访问 vector 的第一百个元素,不过它只有三个元素。这种情况下 Rust 会 panic。[]应当返回一个元素,不过如果传递了一个无效索引,就没有可供 Rust 返回的正确的元素。

+

这种情况下其他像 C 这样语言会尝直接试提供所要求的值,即便这可能不是你期望的:你会得到对任何应 vector 中这个元素的内存位置的值,甚至是这些内存并不属于 vector 的情况。这被称为缓冲区溢出buffer overread),并可能会导致安全漏洞,比如攻击者可以像这样操作索引来读取储存在数组后面不被允许的数据。

+

为了使程序远离这类漏洞,如果尝试读取一个索引不存在的元素,Rust 会停止执行并拒绝继续。尝试运行上面的程序会出现如下:

+
$ cargo run
+   Compiling panic v0.1.0 (file:///projects/panic)
+    Finished debug [unoptimized + debuginfo] target(s) in 0.27 secs
+     Running `target/debug/panic`
+thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
+100', /stable-dist-rustc/build/src/libcollections/vec.rs:1362
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
+
+

这指向了一个不是我们编写的文件,libcollections/vec.rs。这是标准库中Vec<T>的实现。这是当对 vector v使用[]libcollections/vec.rs 中会执行的代码,也是真正出现panic!的地方。

+

接下来的几行提醒我们可以设置RUST_BACKTRACE环境变量来得到一个 backtrace 来调查究竟是什么导致了错误。让我们来试试看。列表 9-1 显示了其输出:

+
+
$ RUST_BACKTRACE=1 cargo run
+    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+     Running `target/debug/panic`
+thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
+100', /stable-dist-rustc/build/src/libcollections/vec.rs:1395
+stack backtrace:
+   1:        0x10922522c -
+std::sys::imp::backtrace::tracing::imp::write::h1204ab053b688140
+   2:        0x10922649e -
+std::panicking::default_hook::{{closure}}::h1204ab053b688140
+   3:        0x109226140 - std::panicking::default_hook::h1204ab053b688140
+   4:        0x109226897 -
+std::panicking::rust_panic_with_hook::h1204ab053b688140
+   5:        0x1092266f4 - std::panicking::begin_panic::h1204ab053b688140
+   6:        0x109226662 - std::panicking::begin_panic_fmt::h1204ab053b688140
+   7:        0x1092265c7 - rust_begin_unwind
+   8:        0x1092486f0 - core::panicking::panic_fmt::h1204ab053b688140
+   9:        0x109248668 -
+core::panicking::panic_bounds_check::h1204ab053b688140
+  10:        0x1092205b5 - <collections::vec::Vec<T> as
+core::ops::Index<usize>>::index::h1204ab053b688140
+  11:        0x10922066a - panic::main::h1204ab053b688140
+  12:        0x1092282ba - __rust_maybe_catch_panic
+  13:        0x109226b16 - std::rt::lang_start::h1204ab053b688140
+  14:        0x1092206e9 - main
+
+
+

Listing 9-1: The backtrace generated by a call to panic! displayed when the +environment variable RUST_BACKTRACE is set

+
+
+

这里有大量的输出!backtrace 第 11 行指向了我们程序中引起错误的行:src/main.rs 的第四行。backtrace 是一个执行到目前位置所有被调用的函数的列表。Rust 的 backtrace 跟其他语言中的一样:阅读 backtrace 的关键是从头开始读直到发现你编写的文件。这就是问题的发源地。这一行往上是你的代码调用的代码;往下则是调用你的代码的代码。这些行可能包含核心 Rust 代码,标准库代码或用到的 crate 代码。

+

如果你不希望我们的程序 panic,第一个提到我们编写的代码行的位置是你应该开始调查的,以便查明是什么值如何在这个地方引起了 panic。在上面的例子中,我们故意编写会 panic 的代码来演示如何使用 backtrace,修复这个 panic 的方法就是不要尝试在一个只包含三个项的 vector 中请求索引是 100 的元素。当将来你得代码出现了 panic,你需要搞清楚在这特定的场景下代码中执行了什么操作和什么值导致了 panic,以及应当如何处理才能避免这个问题。

+

本章的后面会再次回到panic!并讲到何时应该何时不应该使用这个方式。接下来,我们来看看如何使用Result来从错误中恢复。

+ +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch09-02-recoverable-errors-with-result.html b/docs/ch09-02-recoverable-errors-with-result.html new file mode 100644 index 0000000..81e9f8c --- /dev/null +++ b/docs/ch09-02-recoverable-errors-with-result.html @@ -0,0 +1,347 @@ + + + + + Rust 程序设计语言 中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

Result与可恢复的错误

+
+

ch09-01-unrecoverable-errors-with-panic.md +
+commit 0c1d55ef48e5f6cf6a3b221f5b6dd4c922130bb1

+
+

大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并回应的原因失败。例如,如果尝试打开一个文件不过由于文件并不存在而操作就失败,这是我们可能想要创建这个文件而不是终止进程。

+

回忆一下第二章“使用Result类型来处理潜在的错误”部分中的那个Result枚举,它定义有如下连个成员,OkErr

+
enum Result<T, E> {
+    Ok(T),
+    Err(E),
+}
+
+

TE是泛型类型参数;第十章会详细介绍泛型。现在你需要知道的就是T代表成功时返回的Ok成员中的数据的类型,而E代表失败时返回的Err成员中的错误的类型。因为Result有这些泛型类型参数,我们可以将Result类型和标准库中为其定义的函数用于很多不同的场景,这些情况中需要返回的成功值和失败值可能会各不相同。

+

让我们调用一个返回Result的函数,因为它可能会失败:如列表 9-2 所示打开一个文件:

+
+Filename: src/main.rs +
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt");
+}
+
+
+

Listing 9-2: Opening a file

+
+
+

如何知道File::open返回一个Result呢?我们可以查看标准库 API 文档,或者可以直接问编译器!如果给f某个我们知道不是函数返回值类型的类型注解,接着尝试编译代码,编译器会告诉我们类型不匹配。然后错误信息会告诉我们f的类型应该是什么,为此我们将let f语句改为:

+
let f: u32 = File::open("hello.txt");
+
+

现在尝试编译会给出如下错误:

+
error[E0308]: mismatched types
+ --> src/main.rs:4:18
+  |
+4 |     let f: u32 = File::open("hello.txt");
+  |                  ^^^^^^^^^^^^^^^^^^^^^^^ expected u32, found enum
+`std::result::Result`
+  |
+  = note: expected type `u32`
+  = note:    found type `std::result::Result<std::fs::File, std::io::Error>`
+
+

这就告诉我们了File::open函数的返回值类型是Result<T, E>。这里泛型参数T放入了成功值的类型std::fs::File,它是一个文件句柄。E被用在失败值上其类型是std::io::Error

+

这个返回值类型说明File::open调用可能会成功并返回一个可以进行读写的文件句柄。这个函数也可能会失败:例如,文件可能并不存在,或者可能没有访问文件的权限。File::open需要一个方式告诉我们是成功还是失败,并同时提供给我们文件句柄或错误信息。而这些信息正是Result枚举可以提供的。

+

File::open成功的情况下,变量f的值将会是一个包含文件句柄的Ok实例。在失败的情况下,f会是一个包含更多关于出现了何种错误信息的Err实例。

+

我们需要在列表 9-2 的代码中增加根据File::open返回值进行不同处理的逻辑。列表 9-3 展示了一个处理Result的基本工具:第六章学习过的match表达式。

+
+Filename: src/main.rs +
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt");
+
+    let f = match f {
+        Ok(file) => file,
+        Err(error) => {
+            panic!("There was a problem opening the file: {:?}", error)
+        },
+    };
+}
+
+
+

Listing 9-3: Using a match expression to handle the Result variants we +might have

+
+
+

注意与Option枚举一样,Result枚举和其成员也被导入到了 prelude 中,所以就不需要在match分支中的OkErr之前指定Result::

+

这里我们告诉 Rust 当结果是Ok,返回Ok成员中的file值,然后将这个文件句柄赋值给变量fmatch之后,我们可以利用这个文件句柄来进行读写。

+

match的另一个分支处理从File::open得到Err值的情况。在这种情况下,我们选择调用panic!宏。如果当前目录没有一个叫做 hello.txt 的文件,当运行这段代码时会看到如下来自panic!宏的输出:

+
thread 'main' panicked at 'There was a problem opening the file: Error { repr:
+Os { code: 2, message: "No such file or directory" } }', src/main.rs:8
+
+

匹配不同的错误

+

列表 9-3 中的代码不管File::open是因为什么原因失败都会panic!。我们真正希望的是对不同的错误原因采取不同的行为:如果File::open因为文件不存在而失败,我们希望创建这个文件并返回新文件的句柄。如果File::open因为任何其他原因失败,例如没有打开文件的权限,我们仍然希望像列表 9-3 那样panic!。让我们看看列表 9-4,其中match增加了另一个分支:

+
+Filename: src/main.rs +
use std::fs::File;
+use std::io::ErrorKind;
+
+fn main() {
+    let f = File::open("hello.txt");
+
+    let f = match f {
+        Ok(file) => file,
+        Err(ref error) if error.kind() == ErrorKind::NotFound => {
+            match File::create("hello.txt") {
+                Ok(fc) => fc,
+                Err(e) => {
+                    panic!(
+                        "Tried to create file but there was a problem: {:?}",
+                        e
+                    )
+                },
+            }
+        },
+        Err(error) => {
+            panic!(
+                "There was a problem opening the file: {:?}",
+                error
+            )
+        },
+    };
+}
+
+
+

Listing 9-4: Handling different kinds of errors in different ways

+
+
+

File::open返回的Err成员中的值类型io::Error,它是一个标准库中提供的结构体。这个结构体有一个返回io::ErrorKind值的kind方法可供调用。io::ErrorKind是一个标准库提供的枚举,它的成员对应io操作可能导致的不同错误类型。我们感兴趣的成员是ErrorKind::NotFound,它代表尝试打开的文件并不存在。

+

if error.kind() == ErrorKind::NotFound条件被称作 match guard:它是一个进一步完善match分支模式的额外的条件。这个条件必须为真才能使分支的代码被执行;否则,模式匹配会继续并考虑match中的下一个分支。模式中的ref是必须的,这样error就不会被移动到 guard 条件中而只是仅仅引用它。第十八章会详细解释为什么在模式中使用ref而不是&来获取一个引用。简而言之,在模式的上下文中,&匹配一个引用并返回它的值,而ref匹配一个值并返回一个引用。

+

在 match guard 中我们想要检查的条件是error.kind()是否是ErrorKind枚举的NotFound成员。如果是,尝试用File::create创建文件。然而File::create也可能会失败,我们还需要增加一个内部match语句。当文件不能被打开,会打印出一个不同的错误信息。外部match的最后一个分支保持不变这样对任何除了文件不存在的错误会使程序 panic。

+

失败时 panic 的捷径:unwrapexpect

+

match能够胜任它的工作,不过它可能有点冗长并且并不总是能很好的表明意图。Result<T, E>类型定义了很多辅助方法来处理各种情况。其中之一叫做unwrap,它的实现就类似于列表 9-3 中的match语句。如果Result值是成员Okunwrap会返回Ok中的值。如果Result是成员Errunwrap会为我们调用panic!

+
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt").unwrap();
+}
+
+

如果调用这段代码时不存在 hello.txt 文件,我们将会看到一个unwrap调用panic!时提供的错误信息:

+
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error {
+repr: Os { code: 2, message: "No such file or directory" } }',
+/stable-dist-rustc/build/src/libcore/result.rs:868
+
+

还有另一个类似于unwrap的方法它还允许我们选择panic!的错误信息:expect。使用expect而不是unwrap并提供一个好的错误信息可以表明你的意图并有助于追踪 panic 的根源。expect的语法看起来像这样:

+
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt").expect("Failed to open hello.txt");
+}
+
+

expectunwrap的使用方式一样:返回文件句柄或调用panic!宏。expect用来调用panic!的错误信息将会作为传递给expect的参数,而不像unwrap那样使用默认的panic!信息。它看起来像这样:

+
thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
+2, message: "No such file or directory" } }',
+/stable-dist-rustc/build/src/libcore/result.rs:868
+
+

传播错误

+

当编写一个其实现会调用一些可能会失败的操作的函数时,除了在这个函数中处理错误外,还可以选择让调用者知道这个错误并决定该如何处理。这被称为传播propagating)错误,这样能更好的控制代码调用,因为比起你代码所拥有的上下文,调用者可能拥有更多信息或逻辑来决定应该如何处理错误。

+

例如,列表 9-5 展示了一个从文件中读取用户名的函数。如果文件不存在或不能读取,这个函数会将这些错误返回给调用它的代码:

+
+
use std::io;
+use std::io::Read;
+use std::fs::File;
+
+fn read_username_from_file() -> Result<String, io::Error> {
+    let f = File::open("hello.txt");
+
+    let mut f = match f {
+        Ok(file) => file,
+        Err(e) => return Err(e),
+    };
+
+    let mut s = String::new();
+
+    match f.read_to_string(&mut s) {
+        Ok(_) => Ok(s),
+        Err(e) => Err(e),
+    }
+}
+
+
+

Listing 9-5: A function that returns errors to the calling code using match

+
+
+

首先让我们看看函数的返回值:Result<String, io::Error>。这意味着函数返回一个Result<T, E>类型的值,其中泛型参数T的具体类型是String,而E的具体类型是io::Error。如果这个函数没有出任何错误成功返回,函数的调用者会收到一个包含StringOk值————函数从文件中读取到的用户名。如果函数遇到任何错误,函数的调用者会收到一个Err值,它储存了一个包含更多这个问题相关信息的io::Error实例。我们选择io::Error作为函数的返回值是因为它正好是函数体中那两个可能会失败的操作的错误返回值:File::open函数和read_to_string方法。

+

函数体以File::open函数开头。接着使用match处理返回值Result,类似于列表 9-3 中的match,唯一的区别是不再当Err时调用panic!,而是提早返回并将File::open返回的错误值作为函数的错误返回值传递给调用者。如果File::open成功了,我们将文件句柄储存在变量f中并继续。

+

接着我们在变量s中创建了一个新String并调用文件句柄fread_to_string方法来将文件的内容读取到s中。read_to_string方法也返回一个Result因为它也可能会失败:哪怕是File::open已经成功了。所以我们需要另一个match来处理这个Result:如果read_to_string成功了,那么这个函数就成功了,并返回文件中的用户名,它现在位于被封装进Oks中。如果read_to_string失败了,则像之前处理File::open的返回值的match那样返回错误值。并不需要显式的调用return,因为这是函数的最后一个表达式。

+

调用这个函数的代码最终会得到一个包含用户名的Ok值,亦或一个包含io::ErrorErr值。我们无从得知调用者会如何处理这些值。例如,如果他们得到了一个Err值,他们可能会选择panic!并使程序崩溃、使用一个默认的用户名或者从文件之外的地方寻找用户名。我们没有足够的信息知晓调用者具体会如何尝试,所以将所有的成功或失败信息向上传播,让他们选择合适处理方法。

+

这种传播错误的模式在 Rust 是如此的常见,以至于有一个更简便的专用语法:?

+

传播错误的捷径:?

+

列表 9-6 展示了一个read_username_from_file的实现,它实现了与列表 9-5 中的代码相同的功能,不过这个实现是使用了问号运算符:

+
+
use std::io;
+use std::fs::File;
+
+fn read_username_from_file() -> Result<String, io::Error> {
+    let mut f = File::open("hello.txt")?;
+    let mut s = String::new();
+    f.read_to_string(&mut s)?;
+    Ok(s)
+}
+
+
+

Listing 9-6: A function that returns errors to the calling code using ?

+
+
+

Result值之后的?被定义为与列表 9-5 中定义的处理Result值的match表达式有着完全相同的工作方式。如果Result的值是Ok,这个表达式将会返回Ok中的值而程序将继续执行。如果值是ErrErr中的值将作为整个函数的返回值,就好像使用了return关键字一样,这样错误值就被传播给了调用者。

+

在列表 9-6 的上下文中,File::open调用结尾的?将会把Ok中的值返回给变量f。如果出现了错误,?会提早返回整个函数并将任何Err值传播给调用者。同理也适用于read_to_string调用结尾的?

+

?消除了大量样板代码并使得函数的实现更简单。我们甚至可以在?之后直接使用链式方法调用来进一步缩短代码:

+
use std::io;
+use std::io::Read;
+use std::fs::File;
+
+fn read_username_from_file() -> Result<String, io::Error> {
+    let mut s = String::new();
+
+    File::open("hello.txt")?.read_to_string(&mut s)?;
+
+    Ok(s)
+}
+
+

s中创建新的String被放到了函数开头;这没有什么变化。我们对File::open("hello.txt")?的结果直接链式调用了read_to_string,而不再创建变量f。仍然需要read_to_string调用结尾的?,而且当File::openread_to_string都成功没有失败时返回包含用户名sOk值。其功能再一次与列表 9-5 和列表 9-5 保持一致,不过这是一个与众不同且更符合工程学的写法。

+

?只能被用于返回Result的函数

+

?只能被用于返回值类型为Result的函数,因为他被定义为与列表 9-5 中的match表达式有着完全相同的工作方式。matchreturn Err(e)部分要求返回值类型是Result,所以函数的返回值必须是Result才能与这个return相兼容。

+

让我们看看在main函数中使用?会发生什么,如果你还记得的话它的返回值类型是()

+
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt")?;
+}
+
+ + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch09-03-to-panic-or-not-to-panic.html b/docs/ch09-03-to-panic-or-not-to-panic.html new file mode 100644 index 0000000..df5c375 --- /dev/null +++ b/docs/ch09-03-to-panic-or-not-to-panic.html @@ -0,0 +1,202 @@ + + + + + Rust 程序设计语言 中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

panic!还是不panic!

+
+

ch09-03-to-panic-or-not-to-panic.md +
+commit 0c1d55ef48e5f6cf6a3b221f5b6dd4c922130bb1

+
+

那么,该如何决定何时应该panic!以及何时应该返回Result呢?如果代码 panic,就没有恢复的可能。你可以选择对任何错误场景都调用panic!,不管是否有可能恢复,不过这样就你代替调用者决定了这是不可恢复的。选择返回Result值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为Err是不可恢复的,所以他们也可能会调用panic!并将可恢复的错误变成了不可恢复的错误。因此返回Result是定义可能会失败的函数的一个好的默认选择。

+

有一些情况 panic 比返回Result更为合适,不过他们并不常见。让我们讨论一下为何在示例、代码原型和测试中,以及那些人们认为不会失败而编译器不这么看的情况下, panic 是合适的,最后会总结一些在库代码中如何决定是否要 panic 的通用指导原则。

+

示例、代码原型和测试:非常适合 panic

+

当你编写一个示例来展示一些概念时,在拥有健壮的错误处理代码的同时也会使得例子不那么明确。例如,调用一个类似unwrap这样可能panic!的方法可以被理解为一个你实际希望程序处理错误方式的占位符,它根据其余代码运行方式可能会各不相同。

+

类似的,unwrapexpect方法在原型设计时非常方便,在你决定该如何处理错误之前。他们在代码中留下了明显的记号,以便你准备使程序变得更健壮时作为参考。

+

如果方法调用在测试中失败了,我们希望这个测试都失败,即便这个方法并不是需要测试的功能。因为panic!是测试如何被标记为失败的,调用unwrapexpect都是非常有道理的。

+

当你比编译器知道更多的情况

+

当你有一些其他的逻辑来确保Result会是Ok值的时候调用unwrap也是合适的,虽然编译器无法理解这种逻辑。仍然会有一个Result值等着你处理:总的来说你调用的任何操作都有失败的可能性,即便在特定情况下逻辑上是不可能的。如果通过人工检查代码来确保永远也不会出现Err值,那么调用unwrap也是完全可以接受的,这里是一个例子:

+
use std::net::IpAddr;
+
+let home = "127.0.0.1".parse::<IpAddr>().unwrap();
+
+

我们通过解析一个硬编码的字符来创建一个IpAddr实例。可以看出127.0.0.1是一个有效的 IP 地址,所以这里使用unwrap是没有问题的。然而,拥有一个硬编码的有效的字符串也不能改变parse方法的返回值类型:它仍然是一个Result值,而编译器仍然就好像还是有可能出现Err成员那样要求我们处理Result,因为编译器还没有智能到可以识别出这个字符串总是一个有效的 IP 地址。如果 IP 地址字符串来源于用户而不是硬编码进程序中的话,那么就确实有失败的可能性,这时就绝对需要我们以一种更健壮的方式处理Result了。

+

错误处理指导原则

+

在当有可能会导致有害状态的情况下建议使用panic!————在这里,有害状态是指当一些假设、保证、协议或不可变形被打破的状态,例如无效的值、自相矛盾的值或者被传递了不存在的值————外加如下几种情况:

+
    +
  • 有害状态并不包含预期会偶尔发生的错误
  • +
  • 之后的代码的运行依赖于不再处于这种有害状态
  • +
  • 当没有可行的手段来将有害状态信息编码进可用的类型中(并返回)的情况
  • +
+

如果别人调用你的代码并传递了一个没有意义的值,最好的情况也许就是panic!并警告使用你的库的人他的代码中有 bug 以便他能在开发时就修复它。类似的,panic!通常适合调用不能够控制的外部代码时,这时无法修复其返回的无效状态。

+

无论代码编写的多么好,当有害状态是预期会出现时,返回Result仍要比调用panic!更为合适。这样的例子包括解析器接收到错误数据,或者 HTTP 请求返回一个表明触发了限流的状态。在这些例子中,应该通过返回Result来表明失败预期是可能的,这样将有害状态向上传播,这样调用者就可以决定该如何处理这个问题。使用panic!来处理这些情况就不是最好的选择。

+

当代码对值进行操作时,应该首先验证值是有效的,并在其无效时panic!。这主要是出于安全的原因:尝试操作无效数据会暴露代码漏洞,这就是标准库在尝试越界访问数组时会panic!的主要原因:尝试访问不属于当前数据结构的内存是一个常见的安全隐患。函数通常都遵循契约contracts):他们的行为只有在输入满足特定条件时才能得到保证。当违反契约时 panic 是有道理的,因为这这通常代表调用方的 bug,而且这也不是那种你希望必须去处理的错误。事实上也没有合理的方式来恢复调用方的代码:调用方的程序员需要修复他的代码。函数的契约,尤其是当违反它会造成 panic 的契约,应该在函数的 API 文档中得到解释。

+

虽然在所有函数中都拥有许多错误检查是冗长而烦人的。幸运的是,可以利用 Rust 的类型系统(以及编译器的类型检查)为你进行很多检查。如果函数有一个特定类型的参数,可以在知晓编译器已经确保其拥有一个有效值的前提下进行你的代码逻辑。例如,如果你使用了一个不同于Option的类型,而且程序期望它是有值的而不是空值。你的代码无需处理SomeNone这两种情况,它只会有一种情况且绝对会有一个值。尝试向函数传递空值的代码甚至根本不能编译,所以你的函数在运行时没有必要判空。另外一个例子是使用像u32这样的无符号整型,也会确保它永远不为负。

+

创建自定义类型作为验证

+

让我们借用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型作为验证。回忆一下第二章的猜猜看游戏,它的代码请求用户猜测一个 1 到 100 之间的数字,在将其与秘密数字做比较之前我们事实上从未验证用户的猜测是位于这两个数字之间的,只保证它为正。在当前情况下,其影响并不是很严重:“Too high”或“Too low”的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助,例如当用户猜测一个超出范围的数字和输入字母时采取不同的行为。

+

一种实现方式是将猜测解析成i32而不仅仅是u32,来默许输入负数,接着检查数字是否在范围内:

+
loop {
+    // snip
+
+    let guess: i32 = match guess.trim().parse() {
+        Ok(num) => num,
+        Err(_) => continue,
+    };
+
+    if guess < 1 || guess > 100 {
+        println!("The secret number will be between 1 and 100.");
+        continue;
+    }
+
+    match guess.cmp(&secret_number) {
+    // snip
+}
+
+

if表达式检查了值是否超出范围,告诉用户出了什么问题,并调用continue开始下一次循环,请求另一个猜测。if表达式之后,就可以在知道guess在 1 到 100 之间的情况下与秘密数字作比较了。

+

然而,这并不是一个理想的解决方案:程序只处理 1 到 100 之间的值是绝对不可取的,而且如果有很多函数都有这样的要求,在每个函数中都有这样的检查将是非常冗余的(并可能潜在的影响性能)。

+

相反我们可以创建一个新类型来将验证放入创建其实例的函数中,而不是到处重复这些检查。这样就可以安全的在函数签名中使用新类型并相信他们接收到的值。列表 9-8 中展示了一个定义Guess类型的方法,只有在new函数接收到 1 到 100 之间的值时才会创建Guess的实例:

+
+
struct Guess {
+    value: u32,
+}
+
+impl Guess {
+    pub fn new(value: u32) -> Guess {
+        if value < 1 || value > 100 {
+            panic!("Guess value must be between 1 and 100, got {}.", value);
+        }
+
+        Guess {
+            value: value,
+        }
+    }
+
+    pub fn value(&self) -> u32 {
+        self.value
+    }
+}
+
+
+

Listing 9-8: A Guess type that will only continue with values between 1 and +100

+
+
+

首先,我们定义了一个包含u32类型字段value的结构体Guess。这里是储存猜测值的地方。

+

接着在Guess上实现了一个叫做new的关联函数来创建Guess的实例。new定义为接收一个u32类型的参数value并返回一个Guessnew函数中代码的测试确保了其值是在 1 到 100 之间的。如果value没有通过测试则调用panic!,这会警告调用这个函数的程序员有一个需要修改的 bug,因为创建一个value超出范围的Guess将会违反Guess::new所遵循的契约。Guess::new会出现 panic 的条件应该在其公有 API 文档中被提及;第十四章会涉及到在 API 文档中表明panic!可能性的相关规则。如果value通过了测试,我们新建一个Guess,其字段value将被设置为参数value的值,接着返回这个Guess

+

接着,我们实现了一个借用了self的方法value,它没有任何其他参数并返回一个u32。这类方法有时被称为 getter,因为它的目的就是返回对应字段的数据。这样的公有方法是必要的,因为Guess结构体的value字段是私有的。私有的字段value是很重要的,这样使用Guess结构体的代码将不允许直接设置value的值:调用者必须使用Guess::new方法来创建一个Guess的实例,这就确保了不会存在一个value没有通过Guess::new函数的条件检查的Guess

+

如此获取一个参数并只返回 1 到 100 之间数字的函数就可以声明为获取或返回一个Guess,而不是u32,同时其函数体中也无需进行任何额外的检查。

+

总结

+

Rust 的错误处理功能被设计为帮助你编写更加健壮的代码。panic!宏代表一个程序无法处理的状态,并停止执行而不是使用无效或不正确的值继续处理。Rust 类型系统的Result枚举代表操作可能会在一种可以恢复的情况下失败。可以使用Result来告诉代码调用者他需要处理潜在的成功或失败。在适当的场景使用panic!Result将会使你的代码在面对无处不在的错误时显得更加可靠。

+

现在我们已经见识过了标准库中OptionResult泛型枚举的能力了,让我们聊聊泛型是如何工作的,以及如果在你的代码中利用他们。

+ +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch10-00-generics.html b/docs/ch10-00-generics.html new file mode 100644 index 0000000..0356e88 --- /dev/null +++ b/docs/ch10-00-generics.html @@ -0,0 +1,233 @@ + + + + + Rust 程序设计语言 中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

泛型、trait 和生命周期

+
+

ch10-00-generics.md +
+commit b335da755592f286fd97a64d98f0ca3be6a59327

+
+

每一个编程语言都有高效的处理重复概念的工具;在 Rust 中工具之一就是泛型generics)。泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。

+

同理为了编写一份可以用于多种具体值的代码,函数并不知道其参数为何值,这时就可以让函数获取泛型而不是像i32String这样的具体值。我们已经使用过第六章的Option<T>,第八章的Vec<T>HashMap<K, V>,以及第九章的Result<T, E>这些泛型了。本章会探索如何使用泛型定义我们自己自己的类型、函数和方法。

+

首先,我们将回顾一下提取函数以减少代码重复的机制。接着使用一个只在参数类型上不同的泛型函数来实现相同的功能。我们也会讲到结构体和枚举定义中的泛型。

+

之后,我们讨论 traits,这是一个定义泛型行为的方法。trait 可以与泛型结合来将泛型限制为拥有特定行为的类型,而不是任意类型。

+

最后介绍生命周期lifetimes),它是一类允许我们向编译器提供引用如何相互关联的泛型。Rust 的生命周期功能允许在很多场景下借用值同时仍然使编译器能够检查这些引用的有效性。

+

提取函数来减少重复

+

在介绍泛型语法之前,首先来回顾一个不使用泛型的处理重复的技术:提取一个函数。当熟悉了这个技术以后,我们将使用相同的机制来提取一个泛型函数!如同你识别出可以提取到函数中重复代码那样,你也会开始识别出能够使用泛型的重复代码。

+

考虑一下这个寻找列表中最大值的小程序,如列表 10-1 所示:

+
+Filename: src/main.rs +
fn main() {
+    let numbers = vec![34, 50, 25, 100, 65];
+
+    let mut largest = numbers[0];
+
+    for number in numbers {
+        if number > largest {
+            largest = number;
+        }
+    }
+
+    println!("The largest number is {}", largest);
+#  assert_eq!(largest, 100);
+}
+
+
+

Listing 10-1: Code to find the largest number in a list of numbers

+
+
+

这段代码获取一个整型列表,存放在变量numbers中。它将列表的第一项放入了变量largest中。接着遍历了列表中的所有数字,如果当前值大于largest中储存的值,将largest替换为这个值。如果当前值小于目前为止的最大值,largest保持不变。当列表中所有值都被考虑到之后,largest将会是最大值,在这里也就是 100。

+

如果需要在两个不同的列表中寻找最大值,我们可以重复列表 10-1 中的代码这样程序中就会存在两段相同逻辑的代码,如列表 10-2 所示:

+
+Filename: src/main.rs +
fn main() {
+    let numbers = vec![34, 50, 25, 100, 65];
+
+    let mut largest = numbers[0];
+
+    for number in numbers {
+        if number > largest {
+            largest = number;
+        }
+    }
+
+    println!("The largest number is {}", largest);
+
+    let numbers = vec![102, 34, 6000, 89, 54, 2, 43, 8];
+
+    let mut largest = numbers[0];
+
+    for number in numbers {
+        if number > largest {
+            largest = number;
+        }
+    }
+
+    println!("The largest number is {}", largest);
+}
+
+
+

Listing 10-2: Code to find the largest number in two lists of numbers

+
+
+

虽然代码能够执行,但是重复的代码是冗余且已于出错的,并且意味着当更新逻辑时需要修改多处地方的代码。

+ + +

为了消除重复,我们可以创建一层抽象,在这个例子中将表现为一个获取任意整型列表作为参数并对其进行处理的函数。这将增加代码的简洁性并让我们将表达和推导寻找列表中最大值的这个概念与使用这个概念的特定位置相互独。 +立。

+

在列表 10-3 的程序中将寻找最大值的代码提取到了一个叫做largest的函数中。这个程序可以找出两个不同数字列表的最大值,不过列表 10-1 中的代码只存在于一个位置:

+
+Filename: src/main.rs +
fn largest(list: &[i32]) -> i32 {
+    let mut largest = list[0];
+
+    for &item in list.iter() {
+        if item > largest {
+            largest = item;
+        }
+    }
+
+    largest
+}
+
+fn main() {
+    let numbers = vec![34, 50, 25, 100, 65];
+
+    let result = largest(&numbers);
+    println!("The largest number is {}", result);
+#    assert_eq!(result, 100);
+
+    let numbers = vec![102, 34, 6000, 89, 54, 2, 43, 8];
+
+    let result = largest(&numbers);
+    println!("The largest number is {}", result);
+#    assert_eq!(result, 6000);
+}
+
+
+

Listing 10-3: Abstracted code to find the largest number in two lists

+
+
+

这个函数有一个参数list,它代表会传递给函数的任何具体i32值的 slice。函数定义中的list代表任何&[i32]。当调用largest函数时,其代码实际上运行于我们传递的特定值上。

+

从列表 10-2 到列表 10-3 中涉及的机制经历了如下几步:

+
    +
  1. 我们注意到了重复代码。
  2. +
  3. 我们将重复代码提取到了一个函数中,并在函数签名中指定了代码中的输入和返回值。
  4. +
  5. 我们将两个具体的存在重复代码的位置替换为了函数调用。
  6. +
+

在不同的场景使用不同的方式泛型也可以利用相同的步骤来减少重复代码。与函数体中现在作用于一个抽象的list而不是具体值一样,使用泛型的代码也作用于抽象类型。支持泛型背后的概念与你已经了解的支持函数的概念是一样的,不过是实现方式不同。

+

如果我们有两个函数,一个寻找一个i32值的 slice 中的最大项而另一个寻找char值的 slice 中的最大项该怎么办?该如何消除重复呢?让我们拭目以待!

+ +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch10-01-syntax.html b/docs/ch10-01-syntax.html new file mode 100644 index 0000000..287cd85 --- /dev/null +++ b/docs/ch10-01-syntax.html @@ -0,0 +1,173 @@ + + + + + Rust 程序设计语言 中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

泛型数据类型

+
+

ch10-01-syntax.md +
+commit 55d9e75ffec92e922273c997026bb10613a76578

+
+

泛型用于通常我们放置类型的位置,比如函数签名或结构体,允许我们创建可以代替许多具体数据类型的结构体定义。让我们看看如何使用泛型定义函数、结构体、枚举和方法,并且在本部分的结尾我们会讨论泛型代码的性能。

+

在函数定义中使用泛型

+

定义函数时可以在函数签名的参数数据类型和返回值中使用泛型。以这种方式编写的代码将更灵活并能向函数调用者提供更多功能,同时不引入重复代码。

+

回到largest函数上,列表 10-4 中展示了两个提供了相同的寻找 slice 中最大值功能的函数。第一个是从列表 10-3 中提取的寻找 slice 中i32最大值的函数。第二个函数寻找 slice 中char的最大值:

+
+Filename: src/main.rs +
fn largest_i32(list: &[i32]) -> i32 {
+    let mut largest = list[0];
+
+    for &item in list.iter() {
+        if item > largest {
+            largest = item;
+        }
+    }
+
+    largest
+}
+
+fn largest_char(list: &[char]) -> char {
+    let mut largest = list[0];
+
+    for &item in list.iter() {
+        if item > largest {
+            largest = item;
+        }
+    }
+
+    largest
+}
+
+fn main() {
+    let numbers = vec![34, 50, 25, 100, 65];
+
+    let result = largest_i32(&numbers);
+    println!("The largest number is {}", result);
+#    assert_eq!(result, 100);
+
+    let chars = vec!['y', 'm', 'a', 'q'];
+
+    let result = largest_char(&chars);
+    println!("The largest char is {}", result);
+#    assert_eq!(result, 'y');
+}
+
+
+

Listing 10-4: Two functions that differ only in their names and the types in +their signatures

+
+
+

这里largest_i32largest_char有着完全相同的函数体,所以能够将这两个函数变成一个来减少重复就太好了。所幸通过引入一个泛型参数就能实现。

+

为了参数化我们要定义的函数的签名中的类型

+ +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch10-02-traits.html b/docs/ch10-02-traits.html new file mode 100644 index 0000000..f394616 --- /dev/null +++ b/docs/ch10-02-traits.html @@ -0,0 +1,116 @@ + + + + + Rust 程序设计语言 中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+ + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/ch10-03-lifetime-syntax.html b/docs/ch10-03-lifetime-syntax.html new file mode 100644 index 0000000..0e28a9c --- /dev/null +++ b/docs/ch10-03-lifetime-syntax.html @@ -0,0 +1,108 @@ + + + + + Rust 程序设计语言 中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+ + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + diff --git a/docs/index.html b/docs/index.html index a2b979f..c1b7bc4 100644 --- a/docs/index.html +++ b/docs/index.html @@ -46,7 +46,7 @@
diff --git a/docs/print.html b/docs/print.html index b8dcacb..f098d60 100644 --- a/docs/print.html +++ b/docs/print.html @@ -47,7 +47,7 @@
@@ -4109,6 +4109,595 @@ println!("{:?}", map);

标准库 API 文档中描述的这些类型的方法将有助于你进行这些练习!

我们已经开始解除可能会有失败操作的复杂程序了,这也意味着接下来是一个了解错误处理的绝佳时机!

+

错误处理

+
+

ch09-00-error-handling.md +
+commit fc825966fabaa408067eb2df3aa45e4fa6644fb6

+
+

Rust 对可靠性的执着也扩展到了错误处理。错误对于软件来说是不可避免的,所以 Rust 有很多功能来处理当现错误的情况。在很多情况下,Rust 要求你承认出错的可能性可能性并在编译代码之前就采取行动。通过确保不会只有在将代码部署到生产环境之后才会发现错误来使得程序更可靠。

+

Rust 将错误组合成两个主要类别:可恢复错误recoverable)和不可恢复错误unrecoverable)。可恢复错误通常代表向用户报告错误和重试操作是合理的情况,比如未找到文件。不可恢复错误通常是 bug 的同义词,比如尝试访问超过数组结尾的位置。

+

大部分语言并不区分这两类错误,并采用类似异常这样方式统一处理他们。Rust 并没有异常。相反,对于可恢复错误有Result<T, E>值和panic!,它在遇到不可恢复错误时停止程序执行。这一章会首先介绍panic!调用,接着会讲到如何返回Result<T, E>。最后,我们会讨论当决定是尝试从错误中恢复还是停止执行时需要顾及的权衡考虑。

+

panic!与不可恢复的错误

+
+

ch09-01-unrecoverable-errors-with-panic.md +
+commit 380e6ee57c251f5ffa8df4c58b3949405448d914

+
+

突然有一天,糟糕的事情发生了,而你对此束手无策。对于这种情况,Rust 有`panic!宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,并接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。

+
+

Panic 中的栈展开与终止

+

当出现panic!时,程序默认会开始展开unwinding),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接终止abort),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,可以由 panic 时展开切换为终止,通过在 Cargo.toml[profile]部分增加panic = 'abort'。例如,如果你想要在发布模式中 panic 时直接终止:

+
[profile.release]
+panic = 'abort'
+
+
+

让我们在一个简单的程序中调用panic!

+

Filename: src/main.rs

+
fn main() {
+    panic!("crash and burn");
+}
+
+

运行程序将会出现类似这样的输出:

+
$ cargo run
+   Compiling panic v0.1.0 (file:///projects/panic)
+    Finished debug [unoptimized + debuginfo] target(s) in 0.25 secs
+     Running `target/debug/panic`
+thread 'main' panicked at 'crash and burn', src/main.rs:2
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
+
+

最后三行包含panic!造成的错误信息。第一行显示了 panic 提供的信息并指明了源码中 panic 出现的位置:src/main.rs:2 表明这是 src/main.rs 文件的第二行。

+

在这个例子中,被指明的那一行是我们代码的一部分,而且查看这一行的话就会发现panic!宏的调用。换句话说,panic!可能会出现在我们的代码调用的代码中。错误信息报告的文件名和行号可能指向别人代码中的panic!宏调用,而不是我们代码中最终导致panic!的那一行。可以使用panic!被调用的函数的 backtrace 来寻找(我们代码中出问题的地方)。

+

使用panic!backtrace

+

让我们来看看另一个因为我们代码中的 bug 引起的别的库中panic!的例子,而不是直接的宏调用:

+

Filename: src/main.rs

+
fn main() {
+    let v = vec![1, 2, 3];
+
+    v[100];
+}
+
+

这里尝试访问 vector 的第一百个元素,不过它只有三个元素。这种情况下 Rust 会 panic。[]应当返回一个元素,不过如果传递了一个无效索引,就没有可供 Rust 返回的正确的元素。

+

这种情况下其他像 C 这样语言会尝直接试提供所要求的值,即便这可能不是你期望的:你会得到对任何应 vector 中这个元素的内存位置的值,甚至是这些内存并不属于 vector 的情况。这被称为缓冲区溢出buffer overread),并可能会导致安全漏洞,比如攻击者可以像这样操作索引来读取储存在数组后面不被允许的数据。

+

为了使程序远离这类漏洞,如果尝试读取一个索引不存在的元素,Rust 会停止执行并拒绝继续。尝试运行上面的程序会出现如下:

+
$ cargo run
+   Compiling panic v0.1.0 (file:///projects/panic)
+    Finished debug [unoptimized + debuginfo] target(s) in 0.27 secs
+     Running `target/debug/panic`
+thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
+100', /stable-dist-rustc/build/src/libcollections/vec.rs:1362
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
+
+

这指向了一个不是我们编写的文件,libcollections/vec.rs。这是标准库中Vec<T>的实现。这是当对 vector v使用[]libcollections/vec.rs 中会执行的代码,也是真正出现panic!的地方。

+

接下来的几行提醒我们可以设置RUST_BACKTRACE环境变量来得到一个 backtrace 来调查究竟是什么导致了错误。让我们来试试看。列表 9-1 显示了其输出:

+
+
$ RUST_BACKTRACE=1 cargo run
+    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+     Running `target/debug/panic`
+thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
+100', /stable-dist-rustc/build/src/libcollections/vec.rs:1395
+stack backtrace:
+   1:        0x10922522c -
+std::sys::imp::backtrace::tracing::imp::write::h1204ab053b688140
+   2:        0x10922649e -
+std::panicking::default_hook::{{closure}}::h1204ab053b688140
+   3:        0x109226140 - std::panicking::default_hook::h1204ab053b688140
+   4:        0x109226897 -
+std::panicking::rust_panic_with_hook::h1204ab053b688140
+   5:        0x1092266f4 - std::panicking::begin_panic::h1204ab053b688140
+   6:        0x109226662 - std::panicking::begin_panic_fmt::h1204ab053b688140
+   7:        0x1092265c7 - rust_begin_unwind
+   8:        0x1092486f0 - core::panicking::panic_fmt::h1204ab053b688140
+   9:        0x109248668 -
+core::panicking::panic_bounds_check::h1204ab053b688140
+  10:        0x1092205b5 - <collections::vec::Vec<T> as
+core::ops::Index<usize>>::index::h1204ab053b688140
+  11:        0x10922066a - panic::main::h1204ab053b688140
+  12:        0x1092282ba - __rust_maybe_catch_panic
+  13:        0x109226b16 - std::rt::lang_start::h1204ab053b688140
+  14:        0x1092206e9 - main
+
+
+

Listing 9-1: The backtrace generated by a call to panic! displayed when the +environment variable RUST_BACKTRACE is set

+
+
+

这里有大量的输出!backtrace 第 11 行指向了我们程序中引起错误的行:src/main.rs 的第四行。backtrace 是一个执行到目前位置所有被调用的函数的列表。Rust 的 backtrace 跟其他语言中的一样:阅读 backtrace 的关键是从头开始读直到发现你编写的文件。这就是问题的发源地。这一行往上是你的代码调用的代码;往下则是调用你的代码的代码。这些行可能包含核心 Rust 代码,标准库代码或用到的 crate 代码。

+

如果你不希望我们的程序 panic,第一个提到我们编写的代码行的位置是你应该开始调查的,以便查明是什么值如何在这个地方引起了 panic。在上面的例子中,我们故意编写会 panic 的代码来演示如何使用 backtrace,修复这个 panic 的方法就是不要尝试在一个只包含三个项的 vector 中请求索引是 100 的元素。当将来你得代码出现了 panic,你需要搞清楚在这特定的场景下代码中执行了什么操作和什么值导致了 panic,以及应当如何处理才能避免这个问题。

+

本章的后面会再次回到panic!并讲到何时应该何时不应该使用这个方式。接下来,我们来看看如何使用Result来从错误中恢复。

+

Result与可恢复的错误

+
+

ch09-01-unrecoverable-errors-with-panic.md +
+commit 0c1d55ef48e5f6cf6a3b221f5b6dd4c922130bb1

+
+

大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并回应的原因失败。例如,如果尝试打开一个文件不过由于文件并不存在而操作就失败,这是我们可能想要创建这个文件而不是终止进程。

+

回忆一下第二章“使用Result类型来处理潜在的错误”部分中的那个Result枚举,它定义有如下连个成员,OkErr

+
enum Result<T, E> {
+    Ok(T),
+    Err(E),
+}
+
+

TE是泛型类型参数;第十章会详细介绍泛型。现在你需要知道的就是T代表成功时返回的Ok成员中的数据的类型,而E代表失败时返回的Err成员中的错误的类型。因为Result有这些泛型类型参数,我们可以将Result类型和标准库中为其定义的函数用于很多不同的场景,这些情况中需要返回的成功值和失败值可能会各不相同。

+

让我们调用一个返回Result的函数,因为它可能会失败:如列表 9-2 所示打开一个文件:

+
+Filename: src/main.rs +
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt");
+}
+
+
+

Listing 9-2: Opening a file

+
+
+

如何知道File::open返回一个Result呢?我们可以查看标准库 API 文档,或者可以直接问编译器!如果给f某个我们知道不是函数返回值类型的类型注解,接着尝试编译代码,编译器会告诉我们类型不匹配。然后错误信息会告诉我们f的类型应该是什么,为此我们将let f语句改为:

+
let f: u32 = File::open("hello.txt");
+
+

现在尝试编译会给出如下错误:

+
error[E0308]: mismatched types
+ --> src/main.rs:4:18
+  |
+4 |     let f: u32 = File::open("hello.txt");
+  |                  ^^^^^^^^^^^^^^^^^^^^^^^ expected u32, found enum
+`std::result::Result`
+  |
+  = note: expected type `u32`
+  = note:    found type `std::result::Result<std::fs::File, std::io::Error>`
+
+

这就告诉我们了File::open函数的返回值类型是Result<T, E>。这里泛型参数T放入了成功值的类型std::fs::File,它是一个文件句柄。E被用在失败值上其类型是std::io::Error

+

这个返回值类型说明File::open调用可能会成功并返回一个可以进行读写的文件句柄。这个函数也可能会失败:例如,文件可能并不存在,或者可能没有访问文件的权限。File::open需要一个方式告诉我们是成功还是失败,并同时提供给我们文件句柄或错误信息。而这些信息正是Result枚举可以提供的。

+

File::open成功的情况下,变量f的值将会是一个包含文件句柄的Ok实例。在失败的情况下,f会是一个包含更多关于出现了何种错误信息的Err实例。

+

我们需要在列表 9-2 的代码中增加根据File::open返回值进行不同处理的逻辑。列表 9-3 展示了一个处理Result的基本工具:第六章学习过的match表达式。

+
+Filename: src/main.rs +
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt");
+
+    let f = match f {
+        Ok(file) => file,
+        Err(error) => {
+            panic!("There was a problem opening the file: {:?}", error)
+        },
+    };
+}
+
+
+

Listing 9-3: Using a match expression to handle the Result variants we +might have

+
+
+

注意与Option枚举一样,Result枚举和其成员也被导入到了 prelude 中,所以就不需要在match分支中的OkErr之前指定Result::

+

这里我们告诉 Rust 当结果是Ok,返回Ok成员中的file值,然后将这个文件句柄赋值给变量fmatch之后,我们可以利用这个文件句柄来进行读写。

+

match的另一个分支处理从File::open得到Err值的情况。在这种情况下,我们选择调用panic!宏。如果当前目录没有一个叫做 hello.txt 的文件,当运行这段代码时会看到如下来自panic!宏的输出:

+
thread 'main' panicked at 'There was a problem opening the file: Error { repr:
+Os { code: 2, message: "No such file or directory" } }', src/main.rs:8
+
+

匹配不同的错误

+

列表 9-3 中的代码不管File::open是因为什么原因失败都会panic!。我们真正希望的是对不同的错误原因采取不同的行为:如果File::open因为文件不存在而失败,我们希望创建这个文件并返回新文件的句柄。如果File::open因为任何其他原因失败,例如没有打开文件的权限,我们仍然希望像列表 9-3 那样panic!。让我们看看列表 9-4,其中match增加了另一个分支:

+
+Filename: src/main.rs +
use std::fs::File;
+use std::io::ErrorKind;
+
+fn main() {
+    let f = File::open("hello.txt");
+
+    let f = match f {
+        Ok(file) => file,
+        Err(ref error) if error.kind() == ErrorKind::NotFound => {
+            match File::create("hello.txt") {
+                Ok(fc) => fc,
+                Err(e) => {
+                    panic!(
+                        "Tried to create file but there was a problem: {:?}",
+                        e
+                    )
+                },
+            }
+        },
+        Err(error) => {
+            panic!(
+                "There was a problem opening the file: {:?}",
+                error
+            )
+        },
+    };
+}
+
+
+

Listing 9-4: Handling different kinds of errors in different ways

+
+
+

File::open返回的Err成员中的值类型io::Error,它是一个标准库中提供的结构体。这个结构体有一个返回io::ErrorKind值的kind方法可供调用。io::ErrorKind是一个标准库提供的枚举,它的成员对应io操作可能导致的不同错误类型。我们感兴趣的成员是ErrorKind::NotFound,它代表尝试打开的文件并不存在。

+

if error.kind() == ErrorKind::NotFound条件被称作 match guard:它是一个进一步完善match分支模式的额外的条件。这个条件必须为真才能使分支的代码被执行;否则,模式匹配会继续并考虑match中的下一个分支。模式中的ref是必须的,这样error就不会被移动到 guard 条件中而只是仅仅引用它。第十八章会详细解释为什么在模式中使用ref而不是&来获取一个引用。简而言之,在模式的上下文中,&匹配一个引用并返回它的值,而ref匹配一个值并返回一个引用。

+

在 match guard 中我们想要检查的条件是error.kind()是否是ErrorKind枚举的NotFound成员。如果是,尝试用File::create创建文件。然而File::create也可能会失败,我们还需要增加一个内部match语句。当文件不能被打开,会打印出一个不同的错误信息。外部match的最后一个分支保持不变这样对任何除了文件不存在的错误会使程序 panic。

+

失败时 panic 的捷径:unwrapexpect

+

match能够胜任它的工作,不过它可能有点冗长并且并不总是能很好的表明意图。Result<T, E>类型定义了很多辅助方法来处理各种情况。其中之一叫做unwrap,它的实现就类似于列表 9-3 中的match语句。如果Result值是成员Okunwrap会返回Ok中的值。如果Result是成员Errunwrap会为我们调用panic!

+
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt").unwrap();
+}
+
+

如果调用这段代码时不存在 hello.txt 文件,我们将会看到一个unwrap调用panic!时提供的错误信息:

+
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error {
+repr: Os { code: 2, message: "No such file or directory" } }',
+/stable-dist-rustc/build/src/libcore/result.rs:868
+
+

还有另一个类似于unwrap的方法它还允许我们选择panic!的错误信息:expect。使用expect而不是unwrap并提供一个好的错误信息可以表明你的意图并有助于追踪 panic 的根源。expect的语法看起来像这样:

+
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt").expect("Failed to open hello.txt");
+}
+
+

expectunwrap的使用方式一样:返回文件句柄或调用panic!宏。expect用来调用panic!的错误信息将会作为传递给expect的参数,而不像unwrap那样使用默认的panic!信息。它看起来像这样:

+
thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
+2, message: "No such file or directory" } }',
+/stable-dist-rustc/build/src/libcore/result.rs:868
+
+

传播错误

+

当编写一个其实现会调用一些可能会失败的操作的函数时,除了在这个函数中处理错误外,还可以选择让调用者知道这个错误并决定该如何处理。这被称为传播propagating)错误,这样能更好的控制代码调用,因为比起你代码所拥有的上下文,调用者可能拥有更多信息或逻辑来决定应该如何处理错误。

+

例如,列表 9-5 展示了一个从文件中读取用户名的函数。如果文件不存在或不能读取,这个函数会将这些错误返回给调用它的代码:

+
+
use std::io;
+use std::io::Read;
+use std::fs::File;
+
+fn read_username_from_file() -> Result<String, io::Error> {
+    let f = File::open("hello.txt");
+
+    let mut f = match f {
+        Ok(file) => file,
+        Err(e) => return Err(e),
+    };
+
+    let mut s = String::new();
+
+    match f.read_to_string(&mut s) {
+        Ok(_) => Ok(s),
+        Err(e) => Err(e),
+    }
+}
+
+
+

Listing 9-5: A function that returns errors to the calling code using match

+
+
+

首先让我们看看函数的返回值:Result<String, io::Error>。这意味着函数返回一个Result<T, E>类型的值,其中泛型参数T的具体类型是String,而E的具体类型是io::Error。如果这个函数没有出任何错误成功返回,函数的调用者会收到一个包含StringOk值————函数从文件中读取到的用户名。如果函数遇到任何错误,函数的调用者会收到一个Err值,它储存了一个包含更多这个问题相关信息的io::Error实例。我们选择io::Error作为函数的返回值是因为它正好是函数体中那两个可能会失败的操作的错误返回值:File::open函数和read_to_string方法。

+

函数体以File::open函数开头。接着使用match处理返回值Result,类似于列表 9-3 中的match,唯一的区别是不再当Err时调用panic!,而是提早返回并将File::open返回的错误值作为函数的错误返回值传递给调用者。如果File::open成功了,我们将文件句柄储存在变量f中并继续。

+

接着我们在变量s中创建了一个新String并调用文件句柄fread_to_string方法来将文件的内容读取到s中。read_to_string方法也返回一个Result因为它也可能会失败:哪怕是File::open已经成功了。所以我们需要另一个match来处理这个Result:如果read_to_string成功了,那么这个函数就成功了,并返回文件中的用户名,它现在位于被封装进Oks中。如果read_to_string失败了,则像之前处理File::open的返回值的match那样返回错误值。并不需要显式的调用return,因为这是函数的最后一个表达式。

+

调用这个函数的代码最终会得到一个包含用户名的Ok值,亦或一个包含io::ErrorErr值。我们无从得知调用者会如何处理这些值。例如,如果他们得到了一个Err值,他们可能会选择panic!并使程序崩溃、使用一个默认的用户名或者从文件之外的地方寻找用户名。我们没有足够的信息知晓调用者具体会如何尝试,所以将所有的成功或失败信息向上传播,让他们选择合适处理方法。

+

这种传播错误的模式在 Rust 是如此的常见,以至于有一个更简便的专用语法:?

+

传播错误的捷径:?

+

列表 9-6 展示了一个read_username_from_file的实现,它实现了与列表 9-5 中的代码相同的功能,不过这个实现是使用了问号运算符:

+
+
use std::io;
+use std::fs::File;
+
+fn read_username_from_file() -> Result<String, io::Error> {
+    let mut f = File::open("hello.txt")?;
+    let mut s = String::new();
+    f.read_to_string(&mut s)?;
+    Ok(s)
+}
+
+
+

Listing 9-6: A function that returns errors to the calling code using ?

+
+
+

Result值之后的?被定义为与列表 9-5 中定义的处理Result值的match表达式有着完全相同的工作方式。如果Result的值是Ok,这个表达式将会返回Ok中的值而程序将继续执行。如果值是ErrErr中的值将作为整个函数的返回值,就好像使用了return关键字一样,这样错误值就被传播给了调用者。

+

在列表 9-6 的上下文中,File::open调用结尾的?将会把Ok中的值返回给变量f。如果出现了错误,?会提早返回整个函数并将任何Err值传播给调用者。同理也适用于read_to_string调用结尾的?

+

?消除了大量样板代码并使得函数的实现更简单。我们甚至可以在?之后直接使用链式方法调用来进一步缩短代码:

+
use std::io;
+use std::io::Read;
+use std::fs::File;
+
+fn read_username_from_file() -> Result<String, io::Error> {
+    let mut s = String::new();
+
+    File::open("hello.txt")?.read_to_string(&mut s)?;
+
+    Ok(s)
+}
+
+

s中创建新的String被放到了函数开头;这没有什么变化。我们对File::open("hello.txt")?的结果直接链式调用了read_to_string,而不再创建变量f。仍然需要read_to_string调用结尾的?,而且当File::openread_to_string都成功没有失败时返回包含用户名sOk值。其功能再一次与列表 9-5 和列表 9-5 保持一致,不过这是一个与众不同且更符合工程学的写法。

+

?只能被用于返回Result的函数

+

?只能被用于返回值类型为Result的函数,因为他被定义为与列表 9-5 中的match表达式有着完全相同的工作方式。matchreturn Err(e)部分要求返回值类型是Result,所以函数的返回值必须是Result才能与这个return相兼容。

+

让我们看看在main函数中使用?会发生什么,如果你还记得的话它的返回值类型是()

+
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt")?;
+}
+
+ + +

为了消除重复,我们可以创建一层抽象,在这个例子中将表现为一个获取任意整型列表作为参数并对其进行处理的函数。这将增加代码的简洁性并让我们将表达和推导寻找列表中最大值的这个概念与使用这个概念的特定位置相互独。 +立。

+

在列表 10-3 的程序中将寻找最大值的代码提取到了一个叫做largest的函数中。这个程序可以找出两个不同数字列表的最大值,不过列表 10-1 中的代码只存在于一个位置:

+
+Filename: src/main.rs +
fn largest(list: &[i32]) -> i32 {
+    let mut largest = list[0];
+
+    for &item in list.iter() {
+        if item > largest {
+            largest = item;
+        }
+    }
+
+    largest
+}
+
+fn main() {
+    let numbers = vec![34, 50, 25, 100, 65];
+
+    let result = largest(&numbers);
+    println!("The largest number is {}", result);
+#    assert_eq!(result, 100);
+
+    let numbers = vec![102, 34, 6000, 89, 54, 2, 43, 8];
+
+    let result = largest(&numbers);
+    println!("The largest number is {}", result);
+#    assert_eq!(result, 6000);
+}
+
+
+

Listing 10-3: Abstracted code to find the largest number in two lists

+
+
+

这个函数有一个参数list,它代表会传递给函数的任何具体i32值的 slice。函数定义中的list代表任何&[i32]。当调用largest函数时,其代码实际上运行于我们传递的特定值上。

+

从列表 10-2 到列表 10-3 中涉及的机制经历了如下几步:

+
    +
  1. 我们注意到了重复代码。
  2. +
  3. 我们将重复代码提取到了一个函数中,并在函数签名中指定了代码中的输入和返回值。
  4. +
  5. 我们将两个具体的存在重复代码的位置替换为了函数调用。
  6. +
+

在不同的场景使用不同的方式泛型也可以利用相同的步骤来减少重复代码。与函数体中现在作用于一个抽象的list而不是具体值一样,使用泛型的代码也作用于抽象类型。支持泛型背后的概念与你已经了解的支持函数的概念是一样的,不过是实现方式不同。

+

如果我们有两个函数,一个寻找一个i32值的 slice 中的最大项而另一个寻找char值的 slice 中的最大项该怎么办?该如何消除重复呢?让我们拭目以待!

+

泛型数据类型

+
+

ch10-01-syntax.md +
+commit 55d9e75ffec92e922273c997026bb10613a76578

+
+

泛型用于通常我们放置类型的位置,比如函数签名或结构体,允许我们创建可以代替许多具体数据类型的结构体定义。让我们看看如何使用泛型定义函数、结构体、枚举和方法,并且在本部分的结尾我们会讨论泛型代码的性能。

+

在函数定义中使用泛型

+

定义函数时可以在函数签名的参数数据类型和返回值中使用泛型。以这种方式编写的代码将更灵活并能向函数调用者提供更多功能,同时不引入重复代码。

+

回到largest函数上,列表 10-4 中展示了两个提供了相同的寻找 slice 中最大值功能的函数。第一个是从列表 10-3 中提取的寻找 slice 中i32最大值的函数。第二个函数寻找 slice 中char的最大值:

+
+Filename: src/main.rs +
fn largest_i32(list: &[i32]) -> i32 {
+    let mut largest = list[0];
+
+    for &item in list.iter() {
+        if item > largest {
+            largest = item;
+        }
+    }
+
+    largest
+}
+
+fn largest_char(list: &[char]) -> char {
+    let mut largest = list[0];
+
+    for &item in list.iter() {
+        if item > largest {
+            largest = item;
+        }
+    }
+
+    largest
+}
+
+fn main() {
+    let numbers = vec![34, 50, 25, 100, 65];
+
+    let result = largest_i32(&numbers);
+    println!("The largest number is {}", result);
+#    assert_eq!(result, 100);
+
+    let chars = vec!['y', 'm', 'a', 'q'];
+
+    let result = largest_char(&chars);
+    println!("The largest char is {}", result);
+#    assert_eq!(result, 'y');
+}
+
+
+

Listing 10-4: Two functions that differ only in their names and the types in +their signatures

+
+
+

这里largest_i32largest_char有着完全相同的函数体,所以能够将这两个函数变成一个来减少重复就太好了。所幸通过引入一个泛型参数就能实现。

+

为了参数化我们要定义的函数的签名中的类型

diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 577d14a..8b8cb52 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -38,4 +38,14 @@ - [通用集合类型](ch08-00-common-collections.md) - [vector](ch08-01-vectors.md) - [字符串](ch08-02-strings.md) - - [哈希 map](ch08-03-hash-maps.md) \ No newline at end of file + - [哈希 map](ch08-03-hash-maps.md) + +- [错误处理](ch09-00-error-handling.md) + - [`panic!`与不可恢复的错误](ch09-01-unrecoverable-errors-with-panic.md) + - [`Result`与可恢复的错误](ch09-02-recoverable-errors-with-result.md) + - [`panic!`还是不`panic!`](ch09-03-to-panic-or-not-to-panic.md) + +- [泛型、trait 和生命周期](ch10-00-generics.md) + - [泛型数据类型](ch10-01-syntax.md) + - [trait:定义共享的行为](ch10-02-traits.md) + - [生命周期与引用有效性](ch10-03-lifetime-syntax.md) \ No newline at end of file diff --git a/src/ch09-00-error-handling.md b/src/ch09-00-error-handling.md new file mode 100644 index 0000000..0b07078 --- /dev/null +++ b/src/ch09-00-error-handling.md @@ -0,0 +1,11 @@ +# 错误处理 + +> [ch09-00-error-handling.md](https://github.com/rust-lang/book/blob/master/src/ch09-00-error-handling.md) +>
+> commit fc825966fabaa408067eb2df3aa45e4fa6644fb6 + +Rust 对可靠性的执着也扩展到了错误处理。错误对于软件来说是不可避免的,所以 Rust 有很多功能来处理当现错误的情况。在很多情况下,Rust 要求你承认出错的可能性可能性并在编译代码之前就采取行动。通过确保不会只有在将代码部署到生产环境之后才会发现错误来使得程序更可靠。 + +Rust 将错误组合成两个主要类别:**可恢复错误**(*recoverable*)和**不可恢复错误**(*unrecoverable*)。可恢复错误通常代表向用户报告错误和重试操作是合理的情况,比如未找到文件。不可恢复错误通常是 bug 的同义词,比如尝试访问超过数组结尾的位置。 + +大部分语言并不区分这两类错误,并采用类似异常这样方式统一处理他们。Rust 并没有异常。相反,对于可恢复错误有`Result`值和`panic!`,它在遇到不可恢复错误时停止程序执行。这一章会首先介绍`panic!`调用,接着会讲到如何返回`Result`。最后,我们会讨论当决定是尝试从错误中恢复还是停止执行时需要顾及的权衡考虑。 \ No newline at end of file diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md new file mode 100644 index 0000000..aed47ed --- /dev/null +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -0,0 +1,121 @@ +## `panic!`与不可恢复的错误 + +> [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/master/src/ch09-01-unrecoverable-errors-with-panic.md) +>
+> commit 380e6ee57c251f5ffa8df4c58b3949405448d914 + +突然有一天,糟糕的事情发生了,而你对此束手无策。对于这种情况,Rust 有`panic!宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,并接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。 + +> ### Panic 中的栈展开与终止 +> +> 当出现`panic!`时,程序默认会开始**展开**(*unwinding*),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接**终止**(*abort*),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,可以由 panic 时展开切换为终止,通过在 *Cargo.toml* 的`[profile]`部分增加`panic = 'abort'`。例如,如果你想要在发布模式中 panic 时直接终止: +> +> ```toml +> [profile.release] +> panic = 'abort' +> ``` + +让我们在一个简单的程序中调用`panic!`: + +Filename: src/main.rs + +```rust,should_panic +fn main() { + panic!("crash and burn"); +} +``` + +运行程序将会出现类似这样的输出: + +``` +$ cargo run + Compiling panic v0.1.0 (file:///projects/panic) + Finished debug [unoptimized + debuginfo] target(s) in 0.25 secs + Running `target/debug/panic` +thread 'main' panicked at 'crash and burn', src/main.rs:2 +note: Run with `RUST_BACKTRACE=1` for a backtrace. +error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) +``` + +最后三行包含`panic!`造成的错误信息。第一行显示了 panic 提供的信息并指明了源码中 panic 出现的位置:*src/main.rs:2* 表明这是 *src/main.rs* 文件的第二行。 + +在这个例子中,被指明的那一行是我们代码的一部分,而且查看这一行的话就会发现`panic!`宏的调用。换句话说,`panic!`可能会出现在我们的代码调用的代码中。错误信息报告的文件名和行号可能指向别人代码中的`panic!`宏调用,而不是我们代码中最终导致`panic!`的那一行。可以使用`panic!`被调用的函数的 backtrace 来寻找(我们代码中出问题的地方)。 + +### 使用`panic!`backtrace + +让我们来看看另一个因为我们代码中的 bug 引起的别的库中`panic!`的例子,而不是直接的宏调用: + +Filename: src/main.rs + +```rust,should_panic +fn main() { + let v = vec![1, 2, 3]; + + v[100]; +} +``` + +这里尝试访问 vector 的第一百个元素,不过它只有三个元素。这种情况下 Rust 会 panic。`[]`应当返回一个元素,不过如果传递了一个无效索引,就没有可供 Rust 返回的正确的元素。 + +这种情况下其他像 C 这样语言会尝直接试提供所要求的值,即便这可能不是你期望的:你会得到对任何应 vector 中这个元素的内存位置的值,甚至是这些内存并不属于 vector 的情况。这被称为**缓冲区溢出**(*buffer overread*),并可能会导致安全漏洞,比如攻击者可以像这样操作索引来读取储存在数组后面不被允许的数据。 + +为了使程序远离这类漏洞,如果尝试读取一个索引不存在的元素,Rust 会停止执行并拒绝继续。尝试运行上面的程序会出现如下: + +``` +$ cargo run + Compiling panic v0.1.0 (file:///projects/panic) + Finished debug [unoptimized + debuginfo] target(s) in 0.27 secs + Running `target/debug/panic` +thread 'main' panicked at 'index out of bounds: the len is 3 but the index is +100', /stable-dist-rustc/build/src/libcollections/vec.rs:1362 +note: Run with `RUST_BACKTRACE=1` for a backtrace. +error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) +``` + +这指向了一个不是我们编写的文件,*libcollections/vec.rs*。这是标准库中`Vec`的实现。这是当对 vector `v`使用`[]`时 *libcollections/vec.rs* 中会执行的代码,也是真正出现`panic!`的地方。 + +接下来的几行提醒我们可以设置`RUST_BACKTRACE`环境变量来得到一个 backtrace 来调查究竟是什么导致了错误。让我们来试试看。列表 9-1 显示了其输出: + +
+ +``` +$ RUST_BACKTRACE=1 cargo run + Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs + Running `target/debug/panic` +thread 'main' panicked at 'index out of bounds: the len is 3 but the index is +100', /stable-dist-rustc/build/src/libcollections/vec.rs:1395 +stack backtrace: + 1: 0x10922522c - +std::sys::imp::backtrace::tracing::imp::write::h1204ab053b688140 + 2: 0x10922649e - +std::panicking::default_hook::{{closure}}::h1204ab053b688140 + 3: 0x109226140 - std::panicking::default_hook::h1204ab053b688140 + 4: 0x109226897 - +std::panicking::rust_panic_with_hook::h1204ab053b688140 + 5: 0x1092266f4 - std::panicking::begin_panic::h1204ab053b688140 + 6: 0x109226662 - std::panicking::begin_panic_fmt::h1204ab053b688140 + 7: 0x1092265c7 - rust_begin_unwind + 8: 0x1092486f0 - core::panicking::panic_fmt::h1204ab053b688140 + 9: 0x109248668 - +core::panicking::panic_bounds_check::h1204ab053b688140 + 10: 0x1092205b5 - as +core::ops::Index>::index::h1204ab053b688140 + 11: 0x10922066a - panic::main::h1204ab053b688140 + 12: 0x1092282ba - __rust_maybe_catch_panic + 13: 0x109226b16 - std::rt::lang_start::h1204ab053b688140 + 14: 0x1092206e9 - main +``` + +
+ +Listing 9-1: The backtrace generated by a call to `panic!` displayed when the +environment variable `RUST_BACKTRACE` is set + +
+
+ +这里有大量的输出!backtrace 第 11 行指向了我们程序中引起错误的行:*src/main.rs* 的第四行。backtrace 是一个执行到目前位置所有被调用的函数的列表。Rust 的 backtrace 跟其他语言中的一样:阅读 backtrace 的关键是从头开始读直到发现你编写的文件。这就是问题的发源地。这一行往上是你的代码调用的代码;往下则是调用你的代码的代码。这些行可能包含核心 Rust 代码,标准库代码或用到的 crate 代码。 + +如果你不希望我们的程序 panic,第一个提到我们编写的代码行的位置是你应该开始调查的,以便查明是什么值如何在这个地方引起了 panic。在上面的例子中,我们故意编写会 panic 的代码来演示如何使用 backtrace,修复这个 panic 的方法就是不要尝试在一个只包含三个项的 vector 中请求索引是 100 的元素。当将来你得代码出现了 panic,你需要搞清楚在这特定的场景下代码中执行了什么操作和什么值导致了 panic,以及应当如何处理才能避免这个问题。 + +本章的后面会再次回到`panic!`并讲到何时应该何时不应该使用这个方式。接下来,我们来看看如何使用`Result`来从错误中恢复。 \ No newline at end of file diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md new file mode 100644 index 0000000..0063168 --- /dev/null +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -0,0 +1,327 @@ +## `Result`与可恢复的错误 + +> [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/master/src/ch09-02-recoverable-errors-with-result.md) +>
+> commit 0c1d55ef48e5f6cf6a3b221f5b6dd4c922130bb1 + +大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并回应的原因失败。例如,如果尝试打开一个文件不过由于文件并不存在而操作就失败,这是我们可能想要创建这个文件而不是终止进程。 + +回忆一下第二章“使用`Result`类型来处理潜在的错误”部分中的那个`Result`枚举,它定义有如下连个成员,`Ok`和`Err`: + +```rust +enum Result { + Ok(T), + Err(E), +} +``` + +`T`和`E`是泛型类型参数;第十章会详细介绍泛型。现在你需要知道的就是`T`代表成功时返回的`Ok`成员中的数据的类型,而`E`代表失败时返回的`Err`成员中的错误的类型。因为`Result`有这些泛型类型参数,我们可以将`Result`类型和标准库中为其定义的函数用于很多不同的场景,这些情况中需要返回的成功值和失败值可能会各不相同。 + +让我们调用一个返回`Result`的函数,因为它可能会失败:如列表 9-2 所示打开一个文件: + +
+Filename: src/main.rs + +```rust +use std::fs::File; + +fn main() { + let f = File::open("hello.txt"); +} +``` + +
+ +Listing 9-2: Opening a file + +
+
+ +如何知道`File::open`返回一个`Result`呢?我们可以查看标准库 API 文档,或者可以直接问编译器!如果给`f`某个我们知道**不是**函数返回值类型的类型注解,接着尝试编译代码,编译器会告诉我们类型不匹配。然后错误信息会告诉我们`f`的类型**应该**是什么,为此我们将`let f`语句改为: + +```rust,ignore +let f: u32 = File::open("hello.txt"); +``` + +现在尝试编译会给出如下错误: + +``` +error[E0308]: mismatched types + --> src/main.rs:4:18 + | +4 | let f: u32 = File::open("hello.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^ expected u32, found enum +`std::result::Result` + | + = note: expected type `u32` + = note: found type `std::result::Result` +``` + +这就告诉我们了`File::open`函数的返回值类型是`Result`。这里泛型参数`T`放入了成功值的类型`std::fs::File`,它是一个文件句柄。`E`被用在失败值上其类型是`std::io::Error`。 + +这个返回值类型说明`File::open`调用可能会成功并返回一个可以进行读写的文件句柄。这个函数也可能会失败:例如,文件可能并不存在,或者可能没有访问文件的权限。`File::open`需要一个方式告诉我们是成功还是失败,并同时提供给我们文件句柄或错误信息。而这些信息正是`Result`枚举可以提供的。 + +当`File::open`成功的情况下,变量`f`的值将会是一个包含文件句柄的`Ok`实例。在失败的情况下,`f`会是一个包含更多关于出现了何种错误信息的`Err`实例。 + +我们需要在列表 9-2 的代码中增加根据`File::open`返回值进行不同处理的逻辑。列表 9-3 展示了一个处理`Result`的基本工具:第六章学习过的`match`表达式。 + +
+Filename: src/main.rs + +```rust,should_panic +use std::fs::File; + +fn main() { + let f = File::open("hello.txt"); + + let f = match f { + Ok(file) => file, + Err(error) => { + panic!("There was a problem opening the file: {:?}", error) + }, + }; +} +``` + +
+ +Listing 9-3: Using a `match` expression to handle the `Result` variants we +might have + +
+
+ +注意与`Option`枚举一样,`Result`枚举和其成员也被导入到了 prelude 中,所以就不需要在`match`分支中的`Ok`和`Err`之前指定`Result::`。 + +这里我们告诉 Rust 当结果是`Ok`,返回`Ok`成员中的`file`值,然后将这个文件句柄赋值给变量`f`。`match`之后,我们可以利用这个文件句柄来进行读写。 + +`match`的另一个分支处理从`File::open`得到`Err`值的情况。在这种情况下,我们选择调用`panic!`宏。如果当前目录没有一个叫做 *hello.txt* 的文件,当运行这段代码时会看到如下来自`panic!`宏的输出: + +``` +thread 'main' panicked at 'There was a problem opening the file: Error { repr: +Os { code: 2, message: "No such file or directory" } }', src/main.rs:8 +``` + +### 匹配不同的错误 + +列表 9-3 中的代码不管`File::open`是因为什么原因失败都会`panic!`。我们真正希望的是对不同的错误原因采取不同的行为:如果`File::open`因为文件不存在而失败,我们希望创建这个文件并返回新文件的句柄。如果`File::open`因为任何其他原因失败,例如没有打开文件的权限,我们仍然希望像列表 9-3 那样`panic!`。让我们看看列表 9-4,其中`match`增加了另一个分支: + +
+Filename: src/main.rs + +```rust,ignore +use std::fs::File; +use std::io::ErrorKind; + +fn main() { + let f = File::open("hello.txt"); + + let f = match f { + Ok(file) => file, + Err(ref error) if error.kind() == ErrorKind::NotFound => { + match File::create("hello.txt") { + Ok(fc) => fc, + Err(e) => { + panic!( + "Tried to create file but there was a problem: {:?}", + e + ) + }, + } + }, + Err(error) => { + panic!( + "There was a problem opening the file: {:?}", + error + ) + }, + }; +} +``` + +
+ +Listing 9-4: Handling different kinds of errors in different ways + +
+
+ +`File::open`返回的`Err`成员中的值类型`io::Error`,它是一个标准库中提供的结构体。这个结构体有一个返回`io::ErrorKind`值的`kind`方法可供调用。`io::ErrorKind`是一个标准库提供的枚举,它的成员对应`io`操作可能导致的不同错误类型。我们感兴趣的成员是`ErrorKind::NotFound`,它代表尝试打开的文件并不存在。 + +`if error.kind() == ErrorKind::NotFound`条件被称作 *match guard*:它是一个进一步完善`match`分支模式的额外的条件。这个条件必须为真才能使分支的代码被执行;否则,模式匹配会继续并考虑`match`中的下一个分支。模式中的`ref`是必须的,这样`error`就不会被移动到 guard 条件中而只是仅仅引用它。第十八章会详细解释为什么在模式中使用`ref`而不是`&`来获取一个引用。简而言之,在模式的上下文中,`&`匹配一个引用并返回它的值,而`ref`匹配一个值并返回一个引用。 + +在 match guard 中我们想要检查的条件是`error.kind()`是否是`ErrorKind`枚举的`NotFound`成员。如果是,尝试用`File::create`创建文件。然而`File::create`也可能会失败,我们还需要增加一个内部`match`语句。当文件不能被打开,会打印出一个不同的错误信息。外部`match`的最后一个分支保持不变这样对任何除了文件不存在的错误会使程序 panic。 + +### 失败时 panic 的捷径:`unwrap`和`expect` + +`match`能够胜任它的工作,不过它可能有点冗长并且并不总是能很好的表明意图。`Result`类型定义了很多辅助方法来处理各种情况。其中之一叫做`unwrap`,它的实现就类似于列表 9-3 中的`match`语句。如果`Result`值是成员`Ok`,`unwrap`会返回`Ok`中的值。如果`Result`是成员`Err`,`unwrap`会为我们调用`panic!`。 + +```rust,should_panic +use std::fs::File; + +fn main() { + let f = File::open("hello.txt").unwrap(); +} +``` + +如果调用这段代码时不存在 *hello.txt* 文件,我们将会看到一个`unwrap`调用`panic!`时提供的错误信息: + +``` +thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error { +repr: Os { code: 2, message: "No such file or directory" } }', +/stable-dist-rustc/build/src/libcore/result.rs:868 +``` + +还有另一个类似于`unwrap`的方法它还允许我们选择`panic!`的错误信息:`expect`。使用`expect`而不是`unwrap`并提供一个好的错误信息可以表明你的意图并有助于追踪 panic 的根源。`expect`的语法看起来像这样: + +```rust,should_panic +use std::fs::File; + +fn main() { + let f = File::open("hello.txt").expect("Failed to open hello.txt"); +} +``` + +`expect`与`unwrap`的使用方式一样:返回文件句柄或调用`panic!`宏。`expect`用来调用`panic!`的错误信息将会作为传递给`expect`的参数,而不像`unwrap`那样使用默认的`panic!`信息。它看起来像这样: + +``` +thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code: +2, message: "No such file or directory" } }', +/stable-dist-rustc/build/src/libcore/result.rs:868 +``` + +### 传播错误 + +当编写一个其实现会调用一些可能会失败的操作的函数时,除了在这个函数中处理错误外,还可以选择让调用者知道这个错误并决定该如何处理。这被称为**传播**(*propagating*)错误,这样能更好的控制代码调用,因为比起你代码所拥有的上下文,调用者可能拥有更多信息或逻辑来决定应该如何处理错误。 + +例如,列表 9-5 展示了一个从文件中读取用户名的函数。如果文件不存在或不能读取,这个函数会将这些错误返回给调用它的代码: + +
+ +```rust +use std::io; +use std::io::Read; +use std::fs::File; + +fn read_username_from_file() -> Result { + let f = File::open("hello.txt"); + + let mut f = match f { + Ok(file) => file, + Err(e) => return Err(e), + }; + + let mut s = String::new(); + + match f.read_to_string(&mut s) { + Ok(_) => Ok(s), + Err(e) => Err(e), + } +} +``` + +
+ +Listing 9-5: A function that returns errors to the calling code using `match` + +
+
+ +首先让我们看看函数的返回值:`Result`。这意味着函数返回一个`Result`类型的值,其中泛型参数`T`的具体类型是`String`,而`E`的具体类型是`io::Error`。如果这个函数没有出任何错误成功返回,函数的调用者会收到一个包含`String`的`Ok`值————函数从文件中读取到的用户名。如果函数遇到任何错误,函数的调用者会收到一个`Err`值,它储存了一个包含更多这个问题相关信息的`io::Error`实例。我们选择`io::Error`作为函数的返回值是因为它正好是函数体中那两个可能会失败的操作的错误返回值:`File::open`函数和`read_to_string`方法。 + +函数体以`File::open`函数开头。接着使用`match`处理返回值`Result`,类似于列表 9-3 中的`match`,唯一的区别是不再当`Err`时调用`panic!`,而是提早返回并将`File::open`返回的错误值作为函数的错误返回值传递给调用者。如果`File::open`成功了,我们将文件句柄储存在变量`f`中并继续。 + +接着我们在变量`s`中创建了一个新`String`并调用文件句柄`f`的`read_to_string`方法来将文件的内容读取到`s`中。`read_to_string`方法也返回一个`Result`因为它也可能会失败:哪怕是`File::open`已经成功了。所以我们需要另一个`match`来处理这个`Result`:如果`read_to_string`成功了,那么这个函数就成功了,并返回文件中的用户名,它现在位于被封装进`Ok`的`s`中。如果`read_to_string`失败了,则像之前处理`File::open`的返回值的`match`那样返回错误值。并不需要显式的调用`return`,因为这是函数的最后一个表达式。 + +调用这个函数的代码最终会得到一个包含用户名的`Ok`值,亦或一个包含`io::Error`的`Err`值。我们无从得知调用者会如何处理这些值。例如,如果他们得到了一个`Err`值,他们可能会选择`panic!`并使程序崩溃、使用一个默认的用户名或者从文件之外的地方寻找用户名。我们没有足够的信息知晓调用者具体会如何尝试,所以将所有的成功或失败信息向上传播,让他们选择合适处理方法。 + +这种传播错误的模式在 Rust 是如此的常见,以至于有一个更简便的专用语法:`?`。 + +### 传播错误的捷径:`?` + +列表 9-6 展示了一个`read_username_from_file`的实现,它实现了与列表 9-5 中的代码相同的功能,不过这个实现是使用了问号运算符: + +
+ +```rust +use std::io; +use std::fs::File; + +fn read_username_from_file() -> Result { + let mut f = File::open("hello.txt")?; + let mut s = String::new(); + f.read_to_string(&mut s)?; + Ok(s) +} +``` + +
+ +Listing 9-6: A function that returns errors to the calling code using `?` + +
+
+ +`Result`值之后的`?`被定义为与列表 9-5 中定义的处理`Result`值的`match`表达式有着完全相同的工作方式。如果`Result`的值是`Ok`,这个表达式将会返回`Ok`中的值而程序将继续执行。如果值是`Err`,`Err`中的值将作为整个函数的返回值,就好像使用了`return`关键字一样,这样错误值就被传播给了调用者。 + +在列表 9-6 的上下文中,`File::open`调用结尾的`?`将会把`Ok`中的值返回给变量`f`。如果出现了错误,`?`会提早返回整个函数并将任何`Err`值传播给调用者。同理也适用于`read_to_string`调用结尾的`?`。 + +`?`消除了大量样板代码并使得函数的实现更简单。我们甚至可以在`?`之后直接使用链式方法调用来进一步缩短代码: + +```rust +use std::io; +use std::io::Read; +use std::fs::File; + +fn read_username_from_file() -> Result { + let mut s = String::new(); + + File::open("hello.txt")?.read_to_string(&mut s)?; + + Ok(s) +} +``` + +在`s`中创建新的`String`被放到了函数开头;这没有什么变化。我们对`File::open("hello.txt")?`的结果直接链式调用了`read_to_string`,而不再创建变量`f`。仍然需要`read_to_string`调用结尾的`?`,而且当`File::open`和`read_to_string`都成功没有失败时返回包含用户名`s`的`Ok`值。其功能再一次与列表 9-5 和列表 9-5 保持一致,不过这是一个与众不同且更符合工程学的写法。 + +### `?`只能被用于返回`Result`的函数 + +`?`只能被用于返回值类型为`Result`的函数,因为他被定义为与列表 9-5 中的`match`表达式有着完全相同的工作方式。`match`的`return Err(e)`部分要求返回值类型是`Result`,所以函数的返回值必须是`Result`才能与这个`return`相兼容。 + +让我们看看在`main`函数中使用`?`会发生什么,如果你还记得的话它的返回值类型是`()`: + +```rust,ignore +use std::fs::File; + +fn main() { + let f = File::open("hello.txt")?; +} +``` + + + +当编译这些代码,会得到如下错误信息: + +``` +error[E0308]: mismatched types + --> + | +3 | let f = File::open("hello.txt")?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ expected (), found enum +`std::result::Result` + | + = note: expected type `()` + = note: found type `std::result::Result<_, _>` +``` + +错误指出存在不匹配的类型:`main`函数返回一个`()`类型,而`?`返回一个`Result`。编写不返回`Result`的函数时,如果调用其他返回`Result`的函数,需要使用`match`或者`Result`的方法之一来处理它,而不能用`?`将潜在的错误传播给调用者。 + +现在我们讨论过了调用`panic!`或返回`Result`的细节,是时候返回他们各自适合哪些场景的话题了。 \ No newline at end of file diff --git a/src/ch09-03-to-panic-or-not-to-panic.md b/src/ch09-03-to-panic-or-not-to-panic.md new file mode 100644 index 0000000..feaa21b --- /dev/null +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -0,0 +1,122 @@ +## `panic!`还是不`panic!` + +> [ch09-03-to-panic-or-not-to-panic.md](https://github.com/rust-lang/book/blob/master/src/ch09-03-to-panic-or-not-to-panic.md) +>
+> commit 0c1d55ef48e5f6cf6a3b221f5b6dd4c922130bb1 + +那么,该如何决定何时应该`panic!`以及何时应该返回`Result`呢?如果代码 panic,就没有恢复的可能。你可以选择对任何错误场景都调用`panic!`,不管是否有可能恢复,不过这样就你代替调用者决定了这是不可恢复的。选择返回`Result`值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为`Err`是不可恢复的,所以他们也可能会调用`panic!`并将可恢复的错误变成了不可恢复的错误。因此返回`Result`是定义可能会失败的函数的一个好的默认选择。 + +有一些情况 panic 比返回`Result`更为合适,不过他们并不常见。让我们讨论一下为何在示例、代码原型和测试中,以及那些人们认为不会失败而编译器不这么看的情况下, panic 是合适的,最后会总结一些在库代码中如何决定是否要 panic 的通用指导原则。 + +### 示例、代码原型和测试:非常适合 panic + +当你编写一个示例来展示一些概念时,在拥有健壮的错误处理代码的同时也会使得例子不那么明确。例如,调用一个类似`unwrap`这样可能`panic!`的方法可以被理解为一个你实际希望程序处理错误方式的占位符,它根据其余代码运行方式可能会各不相同。 + +类似的,`unwrap`和`expect`方法在原型设计时非常方便,在你决定该如何处理错误之前。他们在代码中留下了明显的记号,以便你准备使程序变得更健壮时作为参考。 + +如果方法调用在测试中失败了,我们希望这个测试都失败,即便这个方法并不是需要测试的功能。因为`panic!`是测试如何被标记为失败的,调用`unwrap`或`expect`都是非常有道理的。 + +### 当你比编译器知道更多的情况 + +当你有一些其他的逻辑来确保`Result`会是`Ok`值的时候调用`unwrap`也是合适的,虽然编译器无法理解这种逻辑。仍然会有一个`Result`值等着你处理:总的来说你调用的任何操作都有失败的可能性,即便在特定情况下逻辑上是不可能的。如果通过人工检查代码来确保永远也不会出现`Err`值,那么调用`unwrap`也是完全可以接受的,这里是一个例子: + +```rust +use std::net::IpAddr; + +let home = "127.0.0.1".parse::().unwrap(); +``` + +我们通过解析一个硬编码的字符来创建一个`IpAddr`实例。可以看出`127.0.0.1`是一个有效的 IP 地址,所以这里使用`unwrap`是没有问题的。然而,拥有一个硬编码的有效的字符串也不能改变`parse`方法的返回值类型:它仍然是一个`Result`值,而编译器仍然就好像还是有可能出现`Err`成员那样要求我们处理`Result`,因为编译器还没有智能到可以识别出这个字符串总是一个有效的 IP 地址。如果 IP 地址字符串来源于用户而不是硬编码进程序中的话,那么就**确实**有失败的可能性,这时就绝对需要我们以一种更健壮的方式处理`Result`了。 + +### 错误处理指导原则 + +在当有可能会导致有害状态的情况下建议使用`panic!`————在这里,有害状态是指当一些假设、保证、协议或不可变形被打破的状态,例如无效的值、自相矛盾的值或者被传递了不存在的值————外加如下几种情况: + +* 有害状态并不包含**预期**会偶尔发生的错误 +* 之后的代码的运行依赖于不再处于这种有害状态 +* 当没有可行的手段来将有害状态信息编码进可用的类型中(并返回)的情况 + +如果别人调用你的代码并传递了一个没有意义的值,最好的情况也许就是`panic!`并警告使用你的库的人他的代码中有 bug 以便他能在开发时就修复它。类似的,`panic!`通常适合调用不能够控制的外部代码时,这时无法修复其返回的无效状态。 + +无论代码编写的多么好,当有害状态是预期会出现时,返回`Result`仍要比调用`panic!`更为合适。这样的例子包括解析器接收到错误数据,或者 HTTP 请求返回一个表明触发了限流的状态。在这些例子中,应该通过返回`Result`来表明失败预期是可能的,这样将有害状态向上传播,这样调用者就可以决定该如何处理这个问题。使用`panic!`来处理这些情况就不是最好的选择。 + +当代码对值进行操作时,应该首先验证值是有效的,并在其无效时`panic!`。这主要是出于安全的原因:尝试操作无效数据会暴露代码漏洞,这就是标准库在尝试越界访问数组时会`panic!`的主要原因:尝试访问不属于当前数据结构的内存是一个常见的安全隐患。函数通常都遵循**契约**(*contracts*):他们的行为只有在输入满足特定条件时才能得到保证。当违反契约时 panic 是有道理的,因为这这通常代表调用方的 bug,而且这也不是那种你希望必须去处理的错误。事实上也没有合理的方式来恢复调用方的代码:调用方的**程序员**需要修复他的代码。函数的契约,尤其是当违反它会造成 panic 的契约,应该在函数的 API 文档中得到解释。 + +虽然在所有函数中都拥有许多错误检查是冗长而烦人的。幸运的是,可以利用 Rust 的类型系统(以及编译器的类型检查)为你进行很多检查。如果函数有一个特定类型的参数,可以在知晓编译器已经确保其拥有一个有效值的前提下进行你的代码逻辑。例如,如果你使用了一个不同于`Option`的类型,而且程序期望它是**有值**的而不是**空值**。你的代码无需处理`Some`和`None`这两种情况,它只会有一种情况且绝对会有一个值。尝试向函数传递空值的代码甚至根本不能编译,所以你的函数在运行时没有必要判空。另外一个例子是使用像`u32`这样的无符号整型,也会确保它永远不为负。 + +### 创建自定义类型作为验证 + +让我们借用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型作为验证。回忆一下第二章的猜猜看游戏,它的代码请求用户猜测一个 1 到 100 之间的数字,在将其与秘密数字做比较之前我们事实上从未验证用户的猜测是位于这两个数字之间的,只保证它为正。在当前情况下,其影响并不是很严重:“Too high”或“Too low”的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助,例如当用户猜测一个超出范围的数字和输入字母时采取不同的行为。 + +一种实现方式是将猜测解析成`i32`而不仅仅是`u32`,来默许输入负数,接着检查数字是否在范围内: + +```rust,ignore +loop { + // snip + + let guess: i32 = match guess.trim().parse() { + Ok(num) => num, + Err(_) => continue, + }; + + if guess < 1 || guess > 100 { + println!("The secret number will be between 1 and 100."); + continue; + } + + match guess.cmp(&secret_number) { + // snip +} +``` + +`if`表达式检查了值是否超出范围,告诉用户出了什么问题,并调用`continue`开始下一次循环,请求另一个猜测。`if`表达式之后,就可以在知道`guess`在 1 到 100 之间的情况下与秘密数字作比较了。 + +然而,这并不是一个理想的解决方案:程序只处理 1 到 100 之间的值是绝对不可取的,而且如果有很多函数都有这样的要求,在每个函数中都有这样的检查将是非常冗余的(并可能潜在的影响性能)。 + +相反我们可以创建一个新类型来将验证放入创建其实例的函数中,而不是到处重复这些检查。这样就可以安全的在函数签名中使用新类型并相信他们接收到的值。列表 9-8 中展示了一个定义`Guess`类型的方法,只有在`new`函数接收到 1 到 100 之间的值时才会创建`Guess`的实例: + +
+ +```rust +struct Guess { + value: u32, +} + +impl Guess { + pub fn new(value: u32) -> Guess { + if value < 1 || value > 100 { + panic!("Guess value must be between 1 and 100, got {}.", value); + } + + Guess { + value: value, + } + } + + pub fn value(&self) -> u32 { + self.value + } +} +``` + +
+ +Listing 9-8: A `Guess` type that will only continue with values between 1 and +100 + +
+
+ +首先,我们定义了一个包含`u32`类型字段`value`的结构体`Guess`。这里是储存猜测值的地方。 + +接着在`Guess`上实现了一个叫做`new`的关联函数来创建`Guess`的实例。`new`定义为接收一个`u32`类型的参数`value`并返回一个`Guess`。`new`函数中代码的测试确保了其值是在 1 到 100 之间的。如果`value`没有通过测试则调用`panic!`,这会警告调用这个函数的程序员有一个需要修改的 bug,因为创建一个`value`超出范围的`Guess`将会违反`Guess::new`所遵循的契约。`Guess::new`会出现 panic 的条件应该在其公有 API 文档中被提及;第十四章会涉及到在 API 文档中表明`panic!`可能性的相关规则。如果`value`通过了测试,我们新建一个`Guess`,其字段`value`将被设置为参数`value`的值,接着返回这个`Guess`。 + +接着,我们实现了一个借用了`self`的方法`value`,它没有任何其他参数并返回一个`u32`。这类方法有时被称为 *getter*,因为它的目的就是返回对应字段的数据。这样的公有方法是必要的,因为`Guess`结构体的`value`字段是私有的。私有的字段`value`是很重要的,这样使用`Guess`结构体的代码将不允许直接设置`value`的值:调用者**必须**使用`Guess::new`方法来创建一个`Guess`的实例,这就确保了不会存在一个`value`没有通过`Guess::new`函数的条件检查的`Guess`。 + +如此获取一个参数并只返回 1 到 100 之间数字的函数就可以声明为获取或返回一个`Guess`,而不是`u32`,同时其函数体中也无需进行任何额外的检查。 + +## 总结 + +Rust 的错误处理功能被设计为帮助你编写更加健壮的代码。`panic!`宏代表一个程序无法处理的状态,并停止执行而不是使用无效或不正确的值继续处理。Rust 类型系统的`Result`枚举代表操作可能会在一种可以恢复的情况下失败。可以使用`Result`来告诉代码调用者他需要处理潜在的成功或失败。在适当的场景使用`panic!`和`Result`将会使你的代码在面对无处不在的错误时显得更加可靠。 + +现在我们已经见识过了标准库中`Option`和`Result`泛型枚举的能力了,让我们聊聊泛型是如何工作的,以及如果在你的代码中利用他们。 \ No newline at end of file diff --git a/src/ch10-00-generics.md b/src/ch10-00-generics.md new file mode 100644 index 0000000..8294a6f --- /dev/null +++ b/src/ch10-00-generics.md @@ -0,0 +1,151 @@ +# 泛型、trait 和生命周期 + +> [ch10-00-generics.md](https://github.com/rust-lang/book/blob/master/src/ch10-00-generics.md) +>
+> commit b335da755592f286fd97a64d98f0ca3be6a59327 + +每一个编程语言都有高效的处理重复概念的工具;在 Rust 中工具之一就是**泛型**(*generics*)。泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。 + +同理为了编写一份可以用于多种具体值的代码,函数并不知道其参数为何值,这时就可以让函数获取泛型而不是像`i32`或`String`这样的具体值。我们已经使用过第六章的`Option`,第八章的`Vec`和`HashMap`,以及第九章的`Result`这些泛型了。本章会探索如何使用泛型定义我们自己自己的类型、函数和方法。 + +首先,我们将回顾一下提取函数以减少代码重复的机制。接着使用一个只在参数类型上不同的泛型函数来实现相同的功能。我们也会讲到结构体和枚举定义中的泛型。 + +之后,我们讨论 *traits*,这是一个定义泛型行为的方法。trait 可以与泛型结合来将泛型限制为拥有特定行为的类型,而不是任意类型。 + +最后介绍**生命周期**(*lifetimes*),它是一类允许我们向编译器提供引用如何相互关联的泛型。Rust 的生命周期功能允许在很多场景下借用值同时仍然使编译器能够检查这些引用的有效性。 + +## 提取函数来减少重复 + +在介绍泛型语法之前,首先来回顾一个不使用泛型的处理重复的技术:提取一个函数。当熟悉了这个技术以后,我们将使用相同的机制来提取一个泛型函数!如同你识别出可以提取到函数中重复代码那样,你也会开始识别出能够使用泛型的重复代码。 + +考虑一下这个寻找列表中最大值的小程序,如列表 10-1 所示: + +
+Filename: src/main.rs + +```rust +fn main() { + let numbers = vec![34, 50, 25, 100, 65]; + + let mut largest = numbers[0]; + + for number in numbers { + if number > largest { + largest = number; + } + } + + println!("The largest number is {}", largest); +# assert_eq!(largest, 100); +} +``` + +
+ +Listing 10-1: Code to find the largest number in a list of numbers + +
+
+ +这段代码获取一个整型列表,存放在变量`numbers`中。它将列表的第一项放入了变量`largest`中。接着遍历了列表中的所有数字,如果当前值大于`largest`中储存的值,将`largest`替换为这个值。如果当前值小于目前为止的最大值,`largest`保持不变。当列表中所有值都被考虑到之后,`largest`将会是最大值,在这里也就是 100。 + +如果需要在两个不同的列表中寻找最大值,我们可以重复列表 10-1 中的代码这样程序中就会存在两段相同逻辑的代码,如列表 10-2 所示: + +
+Filename: src/main.rs + +```rust +fn main() { + let numbers = vec![34, 50, 25, 100, 65]; + + let mut largest = numbers[0]; + + for number in numbers { + if number > largest { + largest = number; + } + } + + println!("The largest number is {}", largest); + + let numbers = vec![102, 34, 6000, 89, 54, 2, 43, 8]; + + let mut largest = numbers[0]; + + for number in numbers { + if number > largest { + largest = number; + } + } + + println!("The largest number is {}", largest); +} +``` + +
+ +Listing 10-2: Code to find the largest number in *two* lists of numbers + +
+
+ +虽然代码能够执行,但是重复的代码是冗余且已于出错的,并且意味着当更新逻辑时需要修改多处地方的代码。 + + + + +为了消除重复,我们可以创建一层抽象,在这个例子中将表现为一个获取任意整型列表作为参数并对其进行处理的函数。这将增加代码的简洁性并让我们将表达和推导寻找列表中最大值的这个概念与使用这个概念的特定位置相互独。 +立。 + +在列表 10-3 的程序中将寻找最大值的代码提取到了一个叫做`largest`的函数中。这个程序可以找出两个不同数字列表的最大值,不过列表 10-1 中的代码只存在于一个位置: + +
+Filename: src/main.rs + +```rust +fn largest(list: &[i32]) -> i32 { + let mut largest = list[0]; + + for &item in list.iter() { + if item > largest { + largest = item; + } + } + + largest +} + +fn main() { + let numbers = vec![34, 50, 25, 100, 65]; + + let result = largest(&numbers); + println!("The largest number is {}", result); +# assert_eq!(result, 100); + + let numbers = vec![102, 34, 6000, 89, 54, 2, 43, 8]; + + let result = largest(&numbers); + println!("The largest number is {}", result); +# assert_eq!(result, 6000); +} +``` + +
+ +Listing 10-3: Abstracted code to find the largest number in two lists + +
+
+ +这个函数有一个参数`list`,它代表会传递给函数的任何具体`i32`值的 slice。函数定义中的`list`代表任何`&[i32]`。当调用`largest`函数时,其代码实际上运行于我们传递的特定值上。 + +从列表 10-2 到列表 10-3 中涉及的机制经历了如下几步: + +1. 我们注意到了重复代码。 +2. 我们将重复代码提取到了一个函数中,并在函数签名中指定了代码中的输入和返回值。 +3. 我们将两个具体的存在重复代码的位置替换为了函数调用。 + +在不同的场景使用不同的方式泛型也可以利用相同的步骤来减少重复代码。与函数体中现在作用于一个抽象的`list`而不是具体值一样,使用泛型的代码也作用于抽象类型。支持泛型背后的概念与你已经了解的支持函数的概念是一样的,不过是实现方式不同。 + +如果我们有两个函数,一个寻找一个`i32`值的 slice 中的最大项而另一个寻找`char`值的 slice 中的最大项该怎么办?该如何消除重复呢?让我们拭目以待! \ No newline at end of file diff --git a/src/ch10-01-syntax.md b/src/ch10-01-syntax.md new file mode 100644 index 0000000..7c09e83 --- /dev/null +++ b/src/ch10-01-syntax.md @@ -0,0 +1,68 @@ +## 泛型数据类型 + +> [ch10-01-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch10-01-syntax.md) +>
+> commit 55d9e75ffec92e922273c997026bb10613a76578 + +泛型用于通常我们放置类型的位置,比如函数签名或结构体,允许我们创建可以代替许多具体数据类型的结构体定义。让我们看看如何使用泛型定义函数、结构体、枚举和方法,并且在本部分的结尾我们会讨论泛型代码的性能。 + +### 在函数定义中使用泛型 + +定义函数时可以在函数签名的参数数据类型和返回值中使用泛型。以这种方式编写的代码将更灵活并能向函数调用者提供更多功能,同时不引入重复代码。 + +回到`largest`函数上,列表 10-4 中展示了两个提供了相同的寻找 slice 中最大值功能的函数。第一个是从列表 10-3 中提取的寻找 slice 中`i32`最大值的函数。第二个函数寻找 slice 中`char`的最大值: + +
+Filename: src/main.rs + +```rust +fn largest_i32(list: &[i32]) -> i32 { + let mut largest = list[0]; + + for &item in list.iter() { + if item > largest { + largest = item; + } + } + + largest +} + +fn largest_char(list: &[char]) -> char { + let mut largest = list[0]; + + for &item in list.iter() { + if item > largest { + largest = item; + } + } + + largest +} + +fn main() { + let numbers = vec![34, 50, 25, 100, 65]; + + let result = largest_i32(&numbers); + println!("The largest number is {}", result); +# assert_eq!(result, 100); + + let chars = vec!['y', 'm', 'a', 'q']; + + let result = largest_char(&chars); + println!("The largest char is {}", result); +# assert_eq!(result, 'y'); +} +``` + +
+ +Listing 10-4: Two functions that differ only in their names and the types in +their signatures + +
+
+ +这里`largest_i32`和`largest_char`有着完全相同的函数体,所以能够将这两个函数变成一个来减少重复就太好了。所幸通过引入一个泛型参数就能实现。 + +为了参数化我们要定义的函数的签名中的类型 \ No newline at end of file diff --git a/src/ch10-02-traits.md b/src/ch10-02-traits.md new file mode 100644 index 0000000..e69de29 diff --git a/src/ch10-03-lifetime-syntax.md b/src/ch10-03-lifetime-syntax.md new file mode 100644 index 0000000..e69de29