@ -1,4 +1,4 @@
## 重构改进模块性和错误处理
## 重构改进模块性和错误处理
> [ch12-03-improving-error-handling-and-modularity.md ](https://github.com/rust-lang/book/blob/master/src/ch12-03-improving-error-handling-and-modularity.md )
> < br >
@ -10,7 +10,7 @@
这同时也关系到第二个问题:`search` 和 `filename` 是程序中的配置变量,而像 `f` 和 `contents` 则用来执行程序逻辑。随着 `main` 函数的增长,就需要引入更多的变量到作用域中,而当作用域中有更多的变量时,将更难以追踪每个变量的目的。最好能将配置变量组织进一个结构,这样就能使他们的目的更明确了。
第三个问题是如果打开文件失败我们使用 `expect` 来打印出错误信息,不过这个错误信息只是说 `file not found` 。除了缺少文件之外还有很多打开文件可能 失败的方式:例如,文件可能存在,不过可能没有打开它的权限。如果我们现在就出于这种情况,打印出的 `file not found` 错误信息就给了用户错误的建议!
第三个问题是如果打开文件失败我们使用 `expect` 来打印出错误信息,不过这个错误信息只是说 `file not found` 。除了缺少文件之外还有很多可以导致 打开文件失败的方式:例如,文件可能存在,不过可能没有打开它的权限。如果我们现在就出于这种情况,打印出的 `file not found` 错误信息就给了用户错误的建议!
第四,我们不停的使用 `expect` 来处理不同的错误,如果用户没有指定足够的参数来运行程序,他们会从 Rust 得到 `index out of bounds` 错误,而这并不能明确的解释问题。如果所有的错误处理都位于一处这样将来的维护者在需要修改错误处理逻辑时就只需要考虑这一处代码。将所有的错误处理都放在一处也有助于确保我们打印的错误信息对终端用户来说是有意义的。
@ -106,7 +106,7 @@ fn parse_config(args: &[String]) -> Config {
< span class = "caption" > 示例 12-6: 重构 `parse_config` 返回一个 `Config` 结构体实例</ span >
`parse_config` 的签名表明它现在返回一个 `Config` 值。在 `parse_config` 的函数体中,之前返回引用了 `args` 中 `String` 值的字符串 slice, 现在我们选择 定义 `Config` 来包含拥有所有权的 `String` 值。`main` 中的 `args` 变量是参数值的所有者并只允许 `parse_config` 函数借用他们,这意味着如果 `Config` 尝试获取 `args` 中值的所有权将违反 Rust 的借用规则。
`parse_config` 的签名表明它现在返回一个 `Config` 值。在之前的 `parse_config` 函数体中,我们返回了引用 `args` 中 `String` 值的字符串 slice, 现在我们定义 `Config` 来包含拥有所有权的 `String` 值。`main` 中的 `args` 变量是参数值的所有者并只允许 `parse_config` 函数借用他们,这意味着如果 `Config` 尝试获取 `args` 中值的所有权将违反 Rust 的借用规则。
还有许多不同的方式可以处理 `String` 的数据,而最简单但有些不太高效的方式是调用这些值的 `clone` 方法。这会生成 `Config` 实例可以拥有的数据的完整拷贝,不过会比储存字符串数据的引用消耗更多的时间和内存。不过拷贝数据使得代码显得更加直白因为无需管理引用的生命周期,所以在这种情况下牺牲一小部分性能来换取简洁性的取舍是值得的。
@ -330,7 +330,7 @@ fn run(config: Config) -> Result<(), Box<dyn Error>> {
这里我们做出了三个明显的修改。首先,将 `run` 函数的返回类型变为 `Result<(), Box<Error>>` 。之前这个函数返回 unit 类型 `()` ,现在它仍然保持作为 `Ok` 时的返回值。
对于错误类型,使用了 **trait 对象** `Box<dyn Error>` (在开头使用了 `use` 语句将 `std::error::Error` 引入作用域)。第十七章会涉及 trait 对象。目前只需知道 `Box<dyn Error>` 意味着函数会返回实现了 `Error` trait 的类型,不过无需指定具体将会返回的值的类型。这提供了在不同的错误场景可能有不同类型的错误返回值的灵活性。这也就是 `dyn` ,它是 “动态”( “dynamic”) 的缩写。
对于错误类型,使用了 **trait 对象** `Box<dyn Error>` (在开头使用了 `use` 语句将 `std::error::Error` 引入作用域)。第十七章会涉及 trait 对象。目前只需知道 `Box<dyn Error>` 意味着函数会返回实现了 `Error` trait 的类型,不过无需指定具体将会返回的值的类型。这提供了在不同的错误场景可能有不同类型的错误返回值的灵活性。这也就是 `dyn` ,它是 “动态的 ”( “dynamic”) 的缩写。
第二个改变是去掉了 `expect` 调用并替换为第九章讲到的 `?` 。不同于遇到错误就 `panic!` , `?` 会从函数中返回错误值并让调用者来处理它。
@ -434,8 +434,8 @@ fn main() {
< span class = "caption" > 示例 12-14: 将 `minigrep` crate 引入 *src/main.rs* 的作用域中</ span >
为了将库 crate 引入二进制 crate, 我们使用 `extern crate minigrep` 。接着增加 `use minigrep::Config` 将 `Config` 类型引入作用域,并使用 crate 名作为 `run` 函数的前缀。通过这些重构,所有功能应该能够联系在一起并运行了。运行 `cargo run` 来确保一切都正确的衔接在一起。
为了将库 crate 引入二进制 crate, 我们使用了 `use minigrep` 。接着 `use minigrep::Config` 将 `Config` 类型引入作用域,并使用 crate 名称 作为 `run` 函数的前缀。通过这些重构,所有功能应该能够联系在一起并运行了。运行 `cargo run` 来确保一切都正确的衔接在一起。
哇哦!这可有很多的工作,不过我们为将来的成功打下了基础。现在处理错误将更容易,同时代码也更加模块化。从现在开始几乎所有的工作都将在 *src/lib.rs* 中进行。
让我们利用这些新创建的模块的优势来进行一些在旧代码中难以展开的工作,他们在新代码中却很简单 :编写测试!
让我们利用这些新创建的模块的优势来进行一些在旧代码中难以展开的工作,这些工作在新代码中非常容易实现,那就是 :编写测试!