<!DOCTYPE HTML> <html lang="en"> <head> <meta charset="UTF-8"> <title>增强错误处理和模块化 - Rust 程序设计语言 简体中文版</title> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> <meta name="description" content="Rust 程序设计语言 简体中文版"> <meta name="viewport" content="width=device-width, initial-scale=1"> <base href=""> <link rel="stylesheet" href="book.css"> <link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'> <link rel="shortcut icon" href="favicon.png"> <!-- Font Awesome --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"> <link rel="stylesheet" href="highlight.css"> <link rel="stylesheet" href="tomorrow-night.css"> <!-- MathJax --> <script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> <!-- Fetch JQuery from CDN but have a local fallback --> <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script> <script> if (typeof jQuery == 'undefined') { document.write(unescape("%3Cscript src='jquery.js'%3E%3C/script%3E")); } </script> </head> <body class="light"> <!-- Set the theme before any content is loaded, prevents flash --> <script type="text/javascript"> var theme = localStorage.getItem('theme'); if (theme == null) { theme = 'light'; } $('body').removeClass().addClass(theme); </script> <!-- Hide / unhide sidebar before it is displayed --> <script type="text/javascript"> var sidebar = localStorage.getItem('sidebar'); if (sidebar === "hidden") { $("html").addClass("sidebar-hidden") } else if (sidebar === "visible") { $("html").addClass("sidebar-visible") } </script> <div id="sidebar" class="sidebar"> <ul class="chapter"><li><a href="ch01-00-introduction.html"><strong>1.</strong> 介绍</a></li><li><ul class="section"><li><a href="ch01-01-installation.html"><strong>1.1.</strong> 安装</a></li><li><a href="ch01-02-hello-world.html"><strong>1.2.</strong> Hello, World!</a></li></ul></li><li><a href="ch02-00-guessing-game-tutorial.html"><strong>2.</strong> 猜猜看教程</a></li><li><a href="ch03-00-common-programming-concepts.html"><strong>3.</strong> 通用编程概念</a></li><li><ul class="section"><li><a href="ch03-01-variables-and-mutability.html"><strong>3.1.</strong> 变量和可变性</a></li><li><a href="ch03-02-data-types.html"><strong>3.2.</strong> 数据类型</a></li><li><a href="ch03-03-how-functions-work.html"><strong>3.3.</strong> 函数如何工作</a></li><li><a href="ch03-04-comments.html"><strong>3.4.</strong> 注释</a></li><li><a href="ch03-05-control-flow.html"><strong>3.5.</strong> 控制流</a></li></ul></li><li><a href="ch04-00-understanding-ownership.html"><strong>4.</strong> 认识所有权</a></li><li><ul class="section"><li><a href="ch04-01-what-is-ownership.html"><strong>4.1.</strong> 什么是所有权</a></li><li><a href="ch04-02-references-and-borrowing.html"><strong>4.2.</strong> 引用 & 借用</a></li><li><a href="ch04-03-slices.html"><strong>4.3.</strong> Slices</a></li></ul></li><li><a href="ch05-00-structs.html"><strong>5.</strong> 结构体</a></li><li><ul class="section"><li><a href="ch05-01-method-syntax.html"><strong>5.1.</strong> 方法语法</a></li></ul></li><li><a href="ch06-00-enums.html"><strong>6.</strong> 枚举和模式匹配</a></li><li><ul class="section"><li><a href="ch06-01-defining-an-enum.html"><strong>6.1.</strong> 定义枚举</a></li><li><a href="ch06-02-match.html"><strong>6.2.</strong> <code>match</code>控制流运算符</a></li><li><a href="ch06-03-if-let.html"><strong>6.3.</strong> <code>if let</code>简单控制流</a></li></ul></li><li><a href="ch07-00-modules.html"><strong>7.</strong> 模块</a></li><li><ul class="section"><li><a href="ch07-01-mod-and-the-filesystem.html"><strong>7.1.</strong> <code>mod</code>和文件系统</a></li><li><a href="ch07-02-controlling-visibility-with-pub.html"><strong>7.2.</strong> 使用<code>pub</code>控制可见性</a></li><li><a href="ch07-03-importing-names-with-use.html"><strong>7.3.</strong> 使用<code>use</code>导入命名</a></li></ul></li><li><a href="ch08-00-common-collections.html"><strong>8.</strong> 通用集合类型</a></li><li><ul class="section"><li><a href="ch08-01-vectors.html"><strong>8.1.</strong> vector</a></li><li><a href="ch08-02-strings.html"><strong>8.2.</strong> 字符串</a></li><li><a href="ch08-03-hash-maps.html"><strong>8.3.</strong> 哈希 map</a></li></ul></li><li><a href="ch09-00-error-handling.html"><strong>9.</strong> 错误处理</a></li><li><ul class="section"><li><a href="ch09-01-unrecoverable-errors-with-panic.html"><strong>9.1.</strong> <code>panic!</code>与不可恢复的错误</a></li><li><a href="ch09-02-recoverable-errors-with-result.html"><strong>9.2.</strong> <code>Result</code>与可恢复的错误</a></li><li><a href="ch09-03-to-panic-or-not-to-panic.html"><strong>9.3.</strong> <code>panic!</code>还是不<code>panic!</code></a></li></ul></li><li><a href="ch10-00-generics.html"><strong>10.</strong> 泛型、trait 和生命周期</a></li><li><ul class="section"><li><a href="ch10-01-syntax.html"><strong>10.1.</strong> 泛型数据类型</a></li><li><a href="ch10-02-traits.html"><strong>10.2.</strong> trait:定义共享的行为</a></li><li><a href="ch10-03-lifetime-syntax.html"><strong>10.3.</strong> 生命周期与引用有效性</a></li></ul></li><li><a href="ch11-00-testing.html"><strong>11.</strong> 测试</a></li><li><ul class="section"><li><a href="ch11-01-writing-tests.html"><strong>11.1.</strong> 编写测试</a></li><li><a href="ch11-02-running-tests.html"><strong>11.2.</strong> 运行测试</a></li><li><a href="ch11-03-test-organization.html"><strong>11.3.</strong> 测试的组织结构</a></li></ul></li><li><a href="ch12-00-an-io-project.html"><strong>12.</strong> 一个 I/O 项目</a></li><li><ul class="section"><li><a href="ch12-01-accepting-command-line-arguments.html"><strong>12.1.</strong> 接受命令行参数</a></li><li><a href="ch12-02-reading-a-file.html"><strong>12.2.</strong> 读取文件</a></li><li><a href="ch12-03-improving-error-handling-and-modularity.html" class="active"><strong>12.3.</strong> 增强错误处理和模块化</a></li><li><a href="ch12-04-testing-the-librarys-functionality.html"><strong>12.4.</strong> 测试库的功能</a></li><li><a href="ch12-05-working-with-environment-variables.html"><strong>12.5.</strong> 处理环境变量</a></li><li><a href="ch12-06-writing-to-stderr-instead-of-stdout.html"><strong>12.6.</strong> 输出到<code>stderr</code>而不是<code>stdout</code></a></li></ul></li><li><a href="ch13-00-functional-features.html"><strong>13.</strong> Rust 中的函数式语言功能</a></li><li><ul class="section"><li><a href="ch13-01-closures.html"><strong>13.1.</strong> 闭包</a></li><li><a href="ch13-02-iterators.html"><strong>13.2.</strong> 迭代器</a></li><li><a href="ch13-03-improving-our-io-project.html"><strong>13.3.</strong> 改进 I/O 项目</a></li><li><a href="ch13-04-performance.html"><strong>13.4.</strong> 性能</a></li></ul></li><li><a href="ch14-00-more-about-cargo.html"><strong>14.</strong> 更多关于 Cargo 和 Crates.io</a></li><li><ul class="section"><li><a href="ch14-01-release-profiles.html"><strong>14.1.</strong> 发布配置</a></li><li><a href="ch14-02-publishing-to-crates-io.html"><strong>14.2.</strong> 将 crate 发布到 Crates.io</a></li><li><a href="ch14-03-cargo-workspaces.html"><strong>14.3.</strong> Cargo 工作空间</a></li><li><a href="ch14-04-installing-binaries.html"><strong>14.4.</strong> 使用<code>cargo install</code>从 Crates.io 安装文件</a></li><li><a href="ch14-05-extending-cargo.html"><strong>14.5.</strong> Cargo 自定义扩展命令</a></li></ul></li><li><a href="ch15-00-smart-pointers.html"><strong>15.</strong> 智能指针</a></li><li><ul class="section"><li><a href="ch15-01-box.html"><strong>15.1.</strong> <code>Box<T></code>用于已知大小的堆上数据</a></li><li><a href="ch15-02-deref.html"><strong>15.2.</strong> <code>Deref</code> Trait 允许通过引用访问数据</a></li><li><a href="ch15-03-drop.html"><strong>15.3.</strong> <code>Drop</code> Trait 运行清理代码</a></li><li><a href="ch15-04-rc.html"><strong>15.4.</strong> <code>Rc<T></code> 引用计数智能指针</a></li><li><a href="ch15-05-interior-mutability.html"><strong>15.5.</strong> <code>RefCell<T></code>和内部可变性模式</a></li><li><a href="ch15-06-reference-cycles.html"><strong>15.6.</strong> 引用循环和内存泄漏是安全的</a></li></ul></li><li><a href="ch16-00-concurrency.html"><strong>16.</strong> 无畏并发</a></li><li><ul class="section"><li><a href="ch16-01-threads.html"><strong>16.1.</strong> 线程</a></li><li><a href="ch16-02-message-passing.html"><strong>16.2.</strong> 消息传递</a></li><li><a href="ch16-03-shared-state.html"><strong>16.3.</strong> 共享状态</a></li><li><a href="ch16-04-extensible-concurrency-sync-and-send.html"><strong>16.4.</strong> 可扩展的并发:<code>Sync</code>和<code>Send</code></a></li></ul></li></ul> </div> <div id="page-wrapper" class="page-wrapper"> <div class="page"> <div id="menu-bar" class="menu-bar"> <div class="left-buttons"> <i id="sidebar-toggle" class="fa fa-bars"></i> <i id="theme-toggle" class="fa fa-paint-brush"></i> </div> <h1 class="menu-title">Rust 程序设计语言 简体中文版</h1> <div class="right-buttons"> <i id="print-button" class="fa fa-print" title="Print this book"></i> </div> </div> <div id="content" class="content"> <a class="header" href="#读取文件" name="读取文件"><h2>读取文件</h2></a> <blockquote> <p><a href="https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-03-improving-error-handling-and-modularity.md">ch12-03-improving-error-handling-and-modularity.md</a> <br> commit bdab3f38da5b7bf7277bfe21ec59a7a81880e6b4</p> </blockquote> <p>为了完善我们程序有四个问题需要修复,而他们都与潜在的错误和程序结构有关。第一个问题是在哪打开文件:我们使用了<code>expect</code>来在打开文件失败时指定一个错误信息,不过这个错误信息只是说“文件不存在”。还有很多打开文件失败的方式,不过我们总是假设是由于缺少文件导致的。例如,文件存在但是没有打开它的权限:这时,我们就打印出了错误不符合事实的错误信息!</p> <p>第二,我们不停的使用<code>expect</code>,这就有点类似我们之前在不传递任何命令行参数时索引会<code>panic!</code>时注意到的问题:这虽然时_可以工作_的,不过这有点没有原则性,而且整个程序中都需要他们,将错误处理都置于一处则会显得好很多。</p> <p>第三个问题是<code>main</code>函数现在处理两个工作:解析参数,并打开文件。对于一个小的函数来说,这不是什么大问题。然而随着程序中的<code>main</code>函数不断增长,<code>main</code>函数中独立的任务也会越来越多。因为一个函数拥有很多职责,它将难以理解、难以测试并难以在不破坏其他部分的情况下做出修改。</p> <p>这也关系到我们的第四个问题:<code>search</code>和<code>filename</code>是程序中配置性的变量,而像<code>f</code>和<code>contents</code>则用来执行程序逻辑。随着<code>main</code>函数增长,将引入更多的变量到作用域中,而当作用域中有更多的变量,将更难以追踪哪个变量用于什么目的。如果能够将配置型变量组织进一个结构就能使他们的目的更明确了。</p> <p>让我们重新组织程序来解决这些问题。</p> <a class="header" href="#二进制项目的关注分离" name="二进制项目的关注分离"><h3>二进制项目的关注分离</h3></a> <p>这类项目组织上的问题在很多相似类型的项目中很常见,所以 Rust 社区开发出一种关注分离的组织模式。这种模式可以用来组织任何用 Rust 构建的二进制项目,所以可以证明应该更早的开始这项重构,以为我们的项目符合这个模式。这个模式看起来像这样:</p> <ol> <li>将程序拆分成 <em>main.rs</em> 和 <em>lib.rs</em>。</li> <li>将命令行参数解析逻辑放入 <em>main.rs</em>。</li> <li>将程序逻辑放入 <em>lib.rs</em>。</li> <li><code>main</code>函数的工作是: <ul> <li>解析参数</li> <li>设置所有配置性变量</li> <li>调用 <em>lib.rs</em> 中的<code>run</code>函数</li> <li>如果<code>run</code>返回错误则处理这个错误</li> </ul> </li> </ol> <p>好的!老实说这个模式好像还很复杂。这就是关注分离的所有内容:<em>main.rs</em> 负责实际的程序运行,而 <em>lib.rs</em> 处理所有真正的任务逻辑。让我们将程序重构成这种模式。首先,提取出一个目的只在于解析参数的函数。列表 12-4 中展示了一个新的开始,<code>main</code>函数调用了一个新函数<code>parse_config</code>,它仍然定义于 <em>src/main.rs</em> 中:</p> <p><span class="filename">Filename: src/main.rs</span></p> <pre><code class="language-rust,ignore">fn main() { let args: Vec<String> = env::args().collect(); let (search, filename) = parse_config(&args); println!("Searching for {}", search); println!("In file {}", filename); // ...snip... } fn parse_config(args: &[String]) -> (&str, &str) { let search = &args[1]; let filename = &args[2]; (search, filename) } </code></pre> <p><span class="caption">Listing 12-4: Extract a <code>parse_config</code> function from <code>main</code></span></p> <!-- Will add ghosting and wingdings in libreoffice /Carol --> <p>这看起来好像有点复杂,不过我们将一点一点的开展重构。在做出这些改变之后,再次运行程序并验证参数解析是否仍然正常。经常验证你的进展是一个好习惯,这样在遇到问题时就能更好地理解什么修改造成了错误。</p> <a class="header" href="#组合配置值" name="组合配置值"><h3>组合配置值</h3></a> <p>现在我们有了一个函数了,让我们接着完善它。我们代码还能设计的更好一些:函数返回了一个元组,不过接着立刻就解构成了单独的部分。这些代码本身没有问题,不过有一个地方表明仍有改善的余地:我们调用了<code>parse_config</code>方法。函数名中的<code>config</code>部分也表明了返回的两个值应该是组合在一起的,因为他们都是某个配置值的一部分。</p> <blockquote> <p>注意:一些同学将当使用符合类型更为合适的时候使用基本类型当作一种称为<strong>基本类型偏执</strong>(<em>primitive obsession</em>)的反模式。</p> </blockquote> <p>让我们引入一个结构体来存放所有的配置。列表 12-5 中展示了新增的<code>Config</code>结构体定义、重构后的<code>parse_config</code>和<code>main</code>函数中的相关更新:</p> <p><span class="filename">Filename: src/main.rs</span></p> <pre><code class="language-rust,ignore">fn main() { let args: Vec<String> = env::args().collect(); let config = parse_config(&args); println!("Searching for {}", config.search); println!("In file {}", config.filename); let mut f = File::open(config.filename).expect("file not found"); // ...snip... } struct Config { search: String, filename: String, } fn parse_config(args: &[String]) -> Config { let search = args[1].clone(); let filename = args[2].clone(); Config { search: search, filename: filename, } } </code></pre> <p><span class="caption">Listing 12-5: Refactoring <code>parse_config</code> to return an instance of a <code>Config</code> struct</span></p> <!-- Will add ghosting and wingdings in libreoffice /Carol --> <p><code>parse_config</code>的签名现在表明它返回一个<code>Config</code>值。在<code>parse_config</code>的函数体中,我们之前返回了<code>args</code>中<code>String</code>值引用的字符串 slice,不过<code>Config</code>定义为拥有两个有所有权的<code>String</code>值。因为<code>parse_config</code>的参数是一个<code>String</code>值的 slice,<code>Config</code>实例不能获取<code>String</code>值的所有权:这违反了 Rust 的借用规则,因为<code>main</code>函数中的<code>args</code>变量拥有这些<code>String</code>值并只允许<code>parse_config</code>函数借用他们。</p> <p>还有许多不同的方式可以处理<code>String</code>的数据;现在我们使用简单但低效率的方式,在字符串 slice 上调用<code>clone</code>方法。<code>clone</code>调用会生成一个字符串数据的完整拷贝,而且<code>Config</code>实例可以拥有它,不过这会消耗更多时间和内存来储存拷贝字符串数据的引用,不过拷贝数据让我们使我们的代码显得更加直白。</p> <!-- PROD: START BOX --> <blockquote> <a class="header" href="#使用clone权衡取舍" name="使用clone权衡取舍"><h4>使用<code>clone</code>权衡取舍</h4></a> <p>由于其运行时消耗,许多 Rustacean 之间有一个趋势是倾向于不使用<code>clone</code>来解决所有权问题。在关于迭代器的第十三章中,我们将会学习如何更有效率的处理这种情况。现在,为了编写我们的程序拷贝一些字符串是没有问题。我们只进行了一次拷贝,而且文件名和要搜索的字符串都比较短。随着你对 Rust 更加熟练,将更轻松的省略这个权衡的步骤,不过现在调用<code>clone</code>是完全可以接受的。</p> </blockquote> <!-- PROD: END BOX --> <p><code>main</code>函数更新为将<code>parse_config</code>返回的<code>Config</code>实例放入变量<code>config</code>中,并将分别使用<code>search</code>和<code>filename</code>变量的代码更新为使用<code>Config</code>结构体的字段。</p> <a class="header" href="#创建一个config构造函数" name="创建一个config构造函数"><h3>创建一个<code>Config</code>构造函数</h3></a> <p>现在让我们考虑一下<code>parse_config</code>的目的:这是一个创建<code>Config</code>示例的函数。我们已经见过了一个创建实例函数的规范:像<code>String::new</code>这样的<code>new</code>函数。列表 12-6 中展示了将<code>parse_config</code>转换为一个<code>Config</code>结构体关联函数<code>new</code>的代码:</p> <p><span class="filename">Filename: src/main.rs</span></p> <pre><code class="language-rust,ignore">fn main() { let args: Vec<String> = env::args().collect(); let config = Config::new(&args); println!("Searching for {}", config.search); println!("In file {}", config.filename); // ...snip... } // ...snip... impl Config { fn new(args: &[String]) -> Config { let search = args[1].clone(); let filename = args[2].clone(); Config { search: search, filename: filename, } } } </code></pre> <p><span class="caption">Listing 12-6: Changing <code>parse_config</code> into <code>Config::new</code></span></p> <!-- Will add ghosting and wingdings in libreoffice /Carol --> <p>我们将<code>parse_config</code>的名字改为<code>new</code>并将其移动到<code>impl</code>块中。我们也更新了<code>main</code>中的调用代码。再次尝试编译并确保程序可以运行。</p> <a class="header" href="#从构造函数返回result" name="从构造函数返回result"><h3>从构造函数返回<code>Result</code></h3></a> <p>这是我们对这个方法最后的重构:还记得当 vector 含有少于三个项时访问索引 1 和 2 会 panic 并给出一个糟糕的错误信息的代码吗?让我们来修改它!列表 12-7 展示了如何在访问这些位置之前检查 slice 是否足够长,并使用一个更好的 panic 信息:</p> <p><span class="filename">Filename: src/main.rs</span></p> <pre><code class="language-rust,ignore">// ...snip... fn new(args: &[String]) -> Config { if args.len() < 3 { panic!("not enough arguments"); } let search = args[1].clone(); // ...snip... } </code></pre> <p><span class="caption">Listing 12-7: Adding a check for the number of arguments</span></p> <!-- Will add ghosting and wingdings in libreoffice /Carol --> <p>通过在<code>new</code>中添加这额外的几行代码,再次尝试不带参数运行程序:</p> <pre><code>$ cargo run Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs Running `target\debug\greprs.exe` thread 'main' panicked at 'not enough arguments', src\main.rs:29 note: Run with `RUST_BACKTRACE=1` for a backtrace. </code></pre> <p>这样就好多了!至少有个一个符合常理的错误信息。然而,还有一堆额外的信息我们并不希望提供给用户。可以通过改变<code>new</code>的签名来完善它。现在它只返回了一个<code>Config</code>,所有没有办法表示创建<code>Config</code>失败的情况。相反,可以如列表 12-8 所示返回一个<code>Result</code>:</p> <p><span class="filename">Filename: src/main.rs</span></p> <pre><code class="language-rust,ignore">impl Config { fn new(args: &[String]) -> Result<Config, &'static str> { if args.len() < 3 { return Err("not enough arguments"); } let search = args[1].clone(); let filename = args[2].clone(); Ok(Config { search: search, filename: filename, }) } } </code></pre> <p><span class="caption">Listing 12-8: Return a <code>Result</code> from <code>Config::new</code></span></p> <!-- Will add ghosting and wingdings in libreoffice /Carol --> <p>现在<code>new</code>函数返回一个<code>Result</code>,在成功时带有一个<code>Config</code>实例而在出现错误时带有一个<code>&'static str</code>。回忆一下第十章“静态声明周期”中讲到<code>&'static str</code>是一个字符串字面值,他也是现在我们的错误信息。</p> <p><code>new</code>函数体中有两处修改:当没有足够参数时不再调用<code>panic!</code>,而是返回<code>Err</code>值。同时我们将<code>Config</code>返回值包装进<code>Ok</code>成员中。这些修改使得函数符合其新的类型签名。</p> <a class="header" href="#confignew调用和错误处理" name="confignew调用和错误处理"><h3><code>Config::new</code>调用和错误处理</h3></a> <p>现在我们需要对<code>main</code>做一些修改,如列表 12-9 所示:</p> <p><span class="filename">Filename: src/main.rs</span></p> <pre><code class="language-rust,ignore">// ...snip... use std::process; fn main() { let args: Vec<String> = env::args().collect(); let config = Config::new(&args).unwrap_or_else(|err| { println!("Problem parsing arguments: {}", err); process::exit(1); }); println!("Searching for {}", config.search); println!("In file {}", config.filename); // ...snip... </code></pre> <p><span class="caption">Listing 12-9: Exiting with an error code if creating a new <code>Config</code> fails</span></p> <!-- Will add ghosting and wingdings in libreoffice /Carol --> <p>新增了一个<code>use</code>行来从标准库中导入<code>process</code>。在<code>main</code>函数中我们将处理<code>new</code>函数返回的<code>Result</code>值,并在其返回<code>Config::new</code>时以一种更加清楚的方式结束进程。</p> <p>这里使用了一个之前没有讲到的标准库中定义的<code>Result<T, E></code>的方法:<code>unwrap_or_else</code>。当<code>Result</code>是<code>Ok</code>时其行为类似于<code>unwrap</code>:它返回<code>Ok</code>内部封装的值。与<code>unwrap</code>不同的是,当<code>Result</code>是<code>Err</code>时,它调用一个<strong>闭包</strong>(<em>closure</em>),也就是一个我们定义的作为参数传递给<code>unwrap_or_else</code>的匿名函数。第十三章会更详细的介绍闭包;这里需要理解的重要部分是<code>unwrap_or_else</code>会将<code>Err</code>的内部值传递给闭包中位于两道竖线间的参数<code>err</code>。使用<code>unwrap_or_else</code>允许我们进行一些自定义的非<code>panic!</code>的错误处理。</p> <p>上述的错误处理其实只有两行:我们打印出了错误,接着调用了<code>std::process::exit</code>。这个函数立刻停止程序的执行并将传递给它的数组作为返回码。依照惯例,零代表成功而任何其他数字表示失败。就结果来说这依然类似于列表 12-7 中的基于<code>panic!</code>的错误处理,但是不再会有额外的输出了,让我们试一试:</p> <pre><code>$ cargo run Compiling greprs v0.1.0 (file:///projects/greprs) Finished debug [unoptimized + debuginfo] target(s) in 0.48 secs Running `target\debug\greprs.exe` Problem parsing arguments: not enough arguments </code></pre> <p>非常好!现在输出就友好多了。</p> <a class="header" href="#run函数中的错误处理" name="run函数中的错误处理"><h3><code>run</code>函数中的错误处理</h3></a> <p>现在重构完了参数解析部分,让我们再改进一下程序的逻辑。列表 12-10 中展示了在<code>main</code>函数中调用提取出函数<code>run</code>之后的代码。<code>run</code>函数包含之前位于<code>main</code>中的部分代码:</p> <p><span class="filename">Filename: src/main.rs</span></p> <pre><code class="language-rust,ignore">fn main() { // ...snip... println!("Searching for {}", config.search); println!("In file {}", config.filename); run(config); } fn run(config: Config) { let mut f = File::open(config.filename).expect("file not found"); let mut contents = String::new(); f.read_to_string(&mut contents).expect("something went wrong reading the file"); println!("With text:\n{}", contents); } // ...snip... </code></pre> <p><span class="caption">Listing 12-10: Extracting a <code>run</code> functionality for the rest of the program logic</span></p> <!-- Will add ghosting and wingdings in libreoffice /Carol --> <p><code>run</code>函数的内容是之前位于<code>main</code>中的几行,而且<code>run</code>函数获取一个<code>Config</code>作为参数。现在有了一个单独的函数了,我们就可以像列表 12-8 中的<code>Config::new</code>那样进行类似的改进了。列表 12-11 展示了另一个<code>use</code>语句将<code>std::error::Error</code>结构引入了作用域,还有使<code>run</code>函数返回<code>Result</code>的修改:</p> <p><span class="filename">Filename: src/main.rs</span></p> <pre><code class="language-rust,ignore">use std::error::Error; // ...snip... fn run(config: Config) -> Result<(), Box<Error>> { let mut f = File::open(config.filename)?; let mut contents = String::new(); f.read_to_string(&mut contents)?; println!("With text:\n{}", contents); Ok(()) } </code></pre> <p><span class="caption">Listing 12-11: Changing the <code>run</code> function to return <code>Result</code></span></p> <!-- Will add ghosting and wingdings in libreoffice /Carol --> <p>这里有三个大的修改。第一个是现在<code>run</code>函数的返回值是<code>Result<(), Box<Error>></code>类型的。之前,函数返回 unit 类型<code>()</code>,现在它仍然是<code>Ok</code>时的返回值。对于错误类型,我们将使用<code>Box<Error></code>。这是一个<strong>trait 对象</strong>(<em>trait object</em>),第XX章会讲到。现在可以这样理解它:<code>Box<Error></code>意味着函数返回了某个实现了<code>Error</code> trait 的类型,不过并没有指定具体的返回值类型。这样就比较灵活,因为在不同的错误场景可能有不同类型的错误返回值。<code>Box</code>是一个堆数据的智能指针,第十五章将会详细介绍<code>Box</code>。</p> <p>第二个改变是我们去掉了<code>expect</code>调用并替换为第9章讲到的<code>?</code>。不同于遇到错误就<code>panic!</code>,这会从函数中返回错误值并让调用者来处理它。</p> <p>第三个修改是现在成功时这个函数会返回一个<code>Ok</code>值。因为<code>run</code>函数签名中声明成功类型返回值是<code>()</code>,所以需要将 unit 类型值包装进<code>Ok</code>值中。<code>Ok(())</code>一开始看起来有点奇怪,不过这样使用<code>()</code>是表明我们调用<code>run</code>只是为了它的副作用的惯用方式;它并没有返回什么有意义的值。</p> <p>上述代码能够编译,不过会有一个警告:</p> <pre><code>warning: unused result which must be used, #[warn(unused_must_use)] on by default --> src\main.rs:39:5 | 39 | run(config); | ^^^^^^^^^^^^ </code></pre> <p>Rust 尝试告诉我们忽略<code>Result</code>,它有可能是一个错误值。让我们现在来处理它。我们将采用类似于列表 12-9 中处理<code>Config::new</code>错误的技巧,不过还有少许不同:</p> <p><span class="filename">Filename: src/main.rs</span></p> <pre><code class="language-rust,ignore">fn main() { // ...snip... println!("Searching for {}", config.search); println!("In file {}", config.filename); if let Err(e) = run(config) { println!("Application error: {}", e); process::exit(1); } } fn run(config: Config) -> Result<(), Box<Error>> { let mut f = File::open(config.filename)?; let mut contents = String::new(); f.read_to_string(&mut contents)?; println!("With text:\n{}", contents); Ok(()) } </code></pre> <!-- Will add ghosting and wingdings in libreoffice /Carol --> <p>不同于<code>unwrap_or_else</code>,我们使用<code>if let</code>来检查<code>run</code>是否返回<code>Err</code>,如果是则调用<code>process::exit(1)</code>。为什么呢?这个例子和<code>Config::new</code>的区别有些微妙。对于<code>Config::new</code>我们关心两件事:</p> <ol> <li>检测出任何可能发生的错误</li> <li>如果没有出现错误创建一个<code>Config</code></li> </ol> <p>而在这个情况下,因为<code>run</code>在成功的时候返回一个<code>()</code>,唯一需要担心的就是第一件事:检测错误。如果我们使用了<code>unwrap_or_else</code>,则会得到<code>()</code>的返回值。它并没有什么用处。</p> <p>虽然两种情况下<code>if let</code>和<code>unwrap_or_else</code>的内容都是一样的:打印出错误并退出。</p> <a class="header" href="#将代码拆分到库-crate" name="将代码拆分到库-crate"><h3>将代码拆分到库 crate</h3></a> <p>现在项目看起来好多了!还有一件我们尚未开始的工作:拆分 <em>src/main.rs</em> 并将一些代码放入 <em>src/lib.rs</em> 中。让我们现在就开始吧:将 <em>src/main.rs</em> 中的<code>run</code>函数移动到新建的 <em>src/lib.rs</em> 中。还需要移动相关的<code>use</code>语句和<code>Config</code>的定义,以及其<code>new</code>方法。现在 <em>src/lib.rs</em> 应该如列表 12-12 所示:</p> <p><span class="filename">Filename: src/lib.rs</span></p> <pre><code class="language-rust,ignore">use std::error::Error; use std::fs::File; use std::io::prelude::*; pub struct Config { pub search: String, pub filename: String, } impl Config { pub fn new(args: &[String]) -> Result<Config, &'static str> { if args.len() < 3 { return Err("not enough arguments"); } let search = args[1].clone(); let filename = args[2].clone(); Ok(Config { search: search, filename: filename, }) } } pub fn run(config: Config) -> Result<(), Box<Error>>{ let mut f = File::open(config.filename)?; let mut contents = String::new(); f.read_to_string(&mut contents)?; println!("With text:\n{}", contents); Ok(()) } </code></pre> <p><span class="caption">Listing 12-12: Moving <code>Config</code> and <code>run</code> into <em>src/lib.rs</em></span></p> <!-- Will add ghosting and wingdings in libreoffice /Carol --> 注意我们还需要使用公有的`pub`:在`Config`和其字段、它的`new`方法和`run`函数上。 <p>现在在 <em>src/main.rs</em> 中,我们需要通过<code>extern crate greprs</code>来引入现在位于 <em>src/lib.rs</em> 的代码。接着需要增加一行<code>use greprs::Config</code>来引入<code>Config</code>到作用域,并对<code>run</code>函数加上 crate 名称前缀,如列表 12-13 所示:</p> <p><span class="filename">Filename: src/main.rs</span></p> <pre><code class="language-rust,ignore">extern crate greprs; use std::env; use std::process; use greprs::Config; fn main() { let args: Vec<String> = env::args().collect(); let config = Config::new(&args).unwrap_or_else(|err| { println!("Problem parsing arguments: {}", err); process::exit(1); }); println!("Searching for {}", config.search); println!("In file {}", config.filename); if let Err(e) = greprs::run(config) { println!("Application error: {}", e); process::exit(1); } } </code></pre> <p><span class="caption">Listing 12-13: Bringing the <code>greprs</code> crate into the scope of <em>src/main.rs</em></span></p> <!-- Will add ghosting and wingdings in libreoffice /Carol --> <!-- Will add ghosting and wingdings in libreoffice /Carol --> <p>通过这些重构,所有代码应该都能运行了。运行几次<code>cargo run</code>来确保你没有破坏什么内容。好的!确实有很多的内容,不过已经为将来的成功奠定了基础。我们采用了一种更加优秀的方式来处理错误,并使得代码更模块化了一些。从现在开始几乎所有的工作都将在 <em>src/lib.rs</em> 中进行。</p> <p>让我们利用这新创建的模块的优势来进行一些在旧代码中难以开开展的工作,他们在新代码中却很简单:编写测试!</p> </div> <!-- Mobile navigation buttons --> <a href="ch12-02-reading-a-file.html" class="mobile-nav-chapters previous"> <i class="fa fa-angle-left"></i> </a> <a href="ch12-04-testing-the-librarys-functionality.html" class="mobile-nav-chapters next"> <i class="fa fa-angle-right"></i> </a> </div> <a href="ch12-02-reading-a-file.html" class="nav-chapters previous" title="You can navigate through the chapters using the arrow keys"> <i class="fa fa-angle-left"></i> </a> <a href="ch12-04-testing-the-librarys-functionality.html" class="nav-chapters next" title="You can navigate through the chapters using the arrow keys"> <i class="fa fa-angle-right"></i> </a> </div> <!-- Local fallback for Font Awesome --> <script> if ($(".fa").css("font-family") !== "FontAwesome") { $('<link rel="stylesheet" type="text/css" href="_FontAwesome/css/font-awesome.css">').prependTo('head'); } </script> <!-- Livereload script (if served using the cli tool) --> <script src="highlight.js"></script> <script src="book.js"></script> </body> </html>