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
枚举,它定义有如下连个成员,Ok
和Err
:
+
enum Result<T, E> {
+ Ok(T),
+ Err(E),
+}
+
+
T
和E
是泛型类型参数;第十章会详细介绍泛型。现在你需要知道的就是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
分支中的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
+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<T, E>
类型定义了很多辅助方法来处理各种情况。其中之一叫做unwrap
,它的实现就类似于列表 9-3 中的match
语句。如果Result
值是成员Ok
,unwrap
会返回Ok
中的值。如果Result
是成员Err
,unwrap
会为我们调用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");
+}
+
+
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 展示了一个从文件中读取用户名的函数。如果文件不存在或不能读取,这个函数会将这些错误返回给调用它的代码:
+
+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
。如果这个函数没有出任何错误成功返回,函数的调用者会收到一个包含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 中的代码相同的功能,不过这个实现是使用了问号运算符:
+
+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
中的值而程序将继续执行。如果值是Err
,Err
中的值将作为整个函数的返回值,就好像使用了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::open
和read_to_string
都成功没有失败时返回包含用户名s
的Ok
值。其功能再一次与列表 9-5 和列表 9-5 保持一致,不过这是一个与众不同且更符合工程学的写法。
+
?
只能被用于返回Result
的函数
+
?
只能被用于返回值类型为Result
的函数,因为他被定义为与列表 9-5 中的match
表达式有着完全相同的工作方式。match
的return 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 中涉及的机制经历了如下几步:
+
+我们注意到了重复代码。
+我们将重复代码提取到了一个函数中,并在函数签名中指定了代码中的输入和返回值。
+我们将两个具体的存在重复代码的位置替换为了函数调用。
+
+
在不同的场景使用不同的方式泛型也可以利用相同的步骤来减少重复代码。与函数体中现在作用于一个抽象的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_i32
和largest_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