From 8d76b29be3676970bc6b482062e2ce5a75931ee2 Mon Sep 17 00:00:00 2001 From: KaiserY Date: Sun, 5 Mar 2017 00:41:41 +0800 Subject: [PATCH] wip --- ...2-01-accepting-command-line-arguments.html | 17 +- docs/ch12-02-reading-a-file.html | 73 +++- ...proving-error-handling-and-modularity.html | 288 +++++++++++++- docs/print.html | 374 +++++++++++++++++- src/PREFACE.md | 10 +- ...h12-01-accepting-command-line-arguments.md | 23 +- src/ch12-02-reading-a-file.md | 89 +++++ ...improving-error-handling-and-modularity.md | 342 ++++++++++++++++ 8 files changed, 1210 insertions(+), 6 deletions(-) diff --git a/docs/ch12-01-accepting-command-line-arguments.html b/docs/ch12-01-accepting-command-line-arguments.html index 7d8a15d..f096d59 100644 --- a/docs/ch12-01-accepting-command-line-arguments.html +++ b/docs/ch12-01-accepting-command-line-arguments.html @@ -125,7 +125,22 @@ fn main() { -

记住,程序名称是是第一个参数,所以并不需要args[0]。我们决定从第一个参数将是需要搜索的字符串,所以

+

记住,程序名称是是第一个参数,所以并不需要args[0]。我们决定从第一个参数将是需要搜索的字符串,所以将第一个参数的引用放入变量search中。第二个参数将是文件名,将其放入变量filename中。再次尝试运行程序:

+
$ cargo run test sample.txt
+    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+     Running `target\debug\greprs.exe test sample.txt`
+Searching for test
+In file sample.txt
+
+

很棒!不过有一个问题。让我们不带参数运行:

+
$ cargo run
+    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+     Running `target\debug\greprs.exe`
+thread 'main' panicked at 'index out of bounds: the len is 1
+but the index is 1', ../src/libcollections\vec.rs:1307
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+
+

因为 vector 中只有一个元素,就是程序名称,不过我们尝试访问第二元素,程序 panic 并提示越界访问。虽然这个错误信息是_准确的_,不过它对程序的用户来说就没有意义了。现在就可以修复这个问题,不过我先继续学习别的内容:在程序结束前我们会改善这个情况。

diff --git a/docs/ch12-02-reading-a-file.html b/docs/ch12-02-reading-a-file.html index 52c5f87..79b3c18 100644 --- a/docs/ch12-02-reading-a-file.html +++ b/docs/ch12-02-reading-a-file.html @@ -67,7 +67,78 @@
- +

读取文件

+
+

ch12-02-reading-a-file.md +
+commit 4f2dc564851dc04b271a2260c834643dfd86c724

+
+

现在有了一些包含我们需要的信息的变量了,让我们试着使用他们。下一步目标是打开需要搜索的文件。为此,我需要一个文件。在项目的根目录创建一个文件poem.txt,并写入一些艾米莉·狄金森(Emily Dickinson)的诗:

+

Filename: poem.txt

+
I'm nobody! Who are you?
+Are you nobody, too?
+Then there's a pair of us — don't tell!
+They'd banish us, you know.
+
+How dreary to be somebody!
+How public, like a frog
+To tell your name the livelong day
+To an admiring bog!
+
+ +

创建完这个文件后,让我们编辑 src/main.rs 并增加如列表 12-3 所示用来打开文件的代码:

+
+Filename: src/main.rs +
use std::env;
+use std::fs::File;
+use std::io::prelude::*;
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+
+    let search = &args[1];
+    let filename = &args[2];
+
+    println!("Searching for {}", search);
+    println!("In file {}", filename);
+
+    let mut f = File::open(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);
+}
+
+
+

Listing 12-3: Read the contents of the file specified by the second argument

+
+
+ +

这里增加了一些新内容。首先,需要更多的use语句来引入标准库中的相关部分:我们需要std::fs::File来处理文件,而std::io::prelude::*则包含许多对于 I/O 包括文件 I/O 有帮助的 trait。类似于 Rust 有一个通用的 prelude 来自动引入特定内容,std::io也有其自己的 prelude 来引入处理 I/O 时需要的内容。不同于默认的 prelude,必须显式use位于std::io中的 prelude。

+

main中,我们增加了三点内容:第一,我们获取了文件的句柄并使用File::open函数与第二个参数中指定的文件名来打开这个文件。第二,我们在变量contents中创建了一个空的可变的String,接着对文件句柄调用read_to_string并以contents字符串作为参数,contentsread_to_string将会放置它读取到的数据地方。最后,我们打印出了整个文件的内容,这是一个确认目前为止的程序能够工作的方法。

+

尝试运行这些代码,随意指定第一个参数(因为还未实现搜索功能的部分)而将 poem.txt 文件将作为第二个参数:

+
$ cargo run the poem.txt
+    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+     Running `target\debug\greprs.exe the poem.txt`
+Searching for the
+In file poem.txt
+With text:
+I'm nobody! Who are you?
+Are you nobody, too?
+Then there's a pair of us — don't tell!
+They'd banish us, you know.
+
+How dreary to be somebody!
+How public, like a frog
+To tell your name the livelong day
+To an admiring bog!
+
+

好的!我们的代码可以工作了!然而,它还有一些瑕疵。因为程序还很小,这些瑕疵并不是什么大问题,不过随着程序功能的丰富,将会越来越难以用简单的方法修复他们。现在就让我们开始重构而不是等待之后处理。重构在只有少量代码时会显得容易得多。

+
diff --git a/docs/ch12-03-improving-error-handling-and-modularity.html b/docs/ch12-03-improving-error-handling-and-modularity.html index f7f48de..530fbc7 100644 --- a/docs/ch12-03-improving-error-handling-and-modularity.html +++ b/docs/ch12-03-improving-error-handling-and-modularity.html @@ -67,7 +67,293 @@
- +

读取文件

+
+

ch12-03-improving-error-handling-and-modularity.md +
+commit 4f2dc564851dc04b271a2260c834643dfd86c724

+
+

为了完善我们程序有四个问题需要修复,而他们都与潜在的错误和程序结构有关。第一个问题是在哪打开文件:我们使用了expect来在打开文件失败时指定一个错误信息,不过这个错误信息只是说“文件不存在”。还有很多打开文件失败的方式,不过我们总是假设是由于缺少文件导致的。例如,文件存在但是没有打开它的权限:这时,我们就打印出了错误不符合事实的错误信息!

+

第二,我们不停的使用expect,这就有点类似我们之前在不传递任何命令行参数时索引会panic!时注意到的问题:这虽然时_可以工作_的,不过这有点没有原则性,而且整个程序中都需要他们,将错误处理都置于一处则会显得好很多。

+

第三个问题是main函数现在处理两个工作:解析参数,并打开文件。对于一个小的函数来说,这不是什么大问题。然而随着程序中的main函数不断增长,main函数中独立的任务也会越来越多。因为一个函数拥有很多职责,它将难以理解、难以测试并难以在不破坏其他部分的情况下做出修改。

+

这也关系到我们的第四个问题:searchfilename是程序中配置性的变量,而像fcontents则用来执行程序逻辑。随着main函数增长,将引入更多的变量到作用域中,而当作用域中有更多的变量,将更难以追踪哪个变量用于什么目的。如果能够将配置型变量组织进一个结构就能使他们的目的更明确了。

+

让我们重新组成程序来解决这些问题。

+

二进制项目的关注分离

+

这类项目组织上的问题在很多相似类型的项目中很常见,所以 Rust 社区开发出一种关注分离的组织模式。这种模式可以用来组织任何用 Rust 构建的二进制项目,所以可以证明应该更早的开始这项重构,以为我们的项目符合这个模式。这个模式看起来像这样:

+
    +
  1. 将程序拆分成 main.rslib.rs
  2. +
  3. 将命令行参数解析逻辑放入 main.rs
  4. +
  5. 将程序逻辑放入 lib.rs
  6. +
  7. main函数的工作是: +
      +
    • 解析参数
    • +
    • 设置所有配置性变量
    • +
    • 调用 lib.rs 中的run函数
    • +
    • 如果run返回错误则处理这个错误
    • +
    +
  8. +
+

好的!老实说这个模式好像还很复杂。这就是关注分离的所有内容:main.rs 负责实际的程序运行,而 lib.rs 处理所有真正的任务逻辑。让我们将程序重构成这种模式。首先,提取出一个目的只在于解析参数的函数。列表 12-4 中展示了一个新的开始,main函数调用了一个新函数parse_config,它仍然定义于 src/main.rs 中:

+
+Filename: src/main.rs +
# use std::env;
+# use std::fs::File;
+# use std::io::prelude::*;
+#
+fn main() {
+    let args: Vec<String> = env::args().collect();
+
+    let (search, filename) = parse_config(&args);
+
+    println!("Searching for {}", search);
+    println!("In file {}", filename);
+
+    // ...snip...
+#
+#     let mut f = File::open(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);
+}
+
+fn parse_config(args: &[String]) -> (&str, &str) {
+    let search = &args[1];
+    let filename = &args[2];
+
+    (search, filename)
+}
+
+
+

Listing 12-4: Extract a parse_config function from main

+
+
+ +

这看起来好像有点复杂,不过我们将一点一点的开展重构。在做出这些改变之后,再次运行程序并验证参数解析是否仍然正常。经常验证你的进展是一个好习惯,这样在遇到问题时就能更好地理解什么修改造成了错误。

+

组合配置值

+

现在我们有了一个函数了,让我们接着完善它。我们代码还能设计的更好一些:函数返回了一个元组,不过接着立刻就解构成了单独的部分。这些代码本身没有问题,不过有一个地方表明仍有改善的余地:我们调用了parse_config方法。函数名中的config部分也表明了返回的两个值应该是组合在一起的,因为他们都是某个配置值的一部分。

+
+

注意:一些同学将当使用符合类型更为合适的时候使用基本类型当作一种称为基本类型偏执primitive obsession)的反模式。

+
+

让我们引入一个结构体来存放所有的配置。列表 12-5 中展示了新增的Config结构体定义、重构后的parse_configmain函数中的相关更新:

+
+Filename: src/main.rs +
# use std::env;
+# use std::fs::File;
+# use std::io::prelude::*;
+#
+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...
+#     let mut contents = String::new();
+#     f.read_to_string(&mut contents).expect("something went wrong reading the file");
+#
+#    println!("With text:\n{}", contents);
+}
+
+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,
+    }
+}
+
+
+

Listing 12-5: Refactoring parse_config to return an instance of a Config +struct

+
+
+ +

parse_config的签名现在表明它返回一个Config值。在parse_config的函数体中,我们之前返回了argsString值引用的字符串 slice,不过Config定义为拥有两个有所有权的String值。因为parse_config的参数是一个String值的 slice,Config实例不能获取String值的所有权:这违反了 Rust 的借用规则,因为main函数中的args变量拥有这些String值并只允许parse_config函数借用他们。

+

还有许多不同的方式可以处理String的数据;现在我们使用简单但低效率的方式,在字符串 slice 上调用clone方法。clone调用会生成一个字符串数据的完整拷贝,而且Config实例可以拥有它,不过这会消耗更多时间和内存来储存拷贝字符串数据的引用,不过拷贝数据让我们使我们的代码显得更加直白。

+ +
+

使用clone权衡取舍

+

由于其运行时消耗,许多 Rustacean 之间有一个趋势是倾向于不使用clone来解决所有权问题。在关于迭代器的第XX章中,我们将会学习如何更有效率的处理这种情况。现在,为了编写我们的程序拷贝一些字符串是没有问题。我们只进行了一次拷贝,而且文件名和要搜索的字符串都比较短。随着你对 Rust 更加熟练,将更轻松的省略这个权衡的步骤,不过现在调用clone是完全可以接受的。

+
+ +

main函数更新为将parse_config返回的Config实例放入变量config中,并将分别使用searchfilename变量的代码更新为使用Config结构体的字段。

+

创建一个Config构造函数

+

现在让我们考虑一下parse_config的目的:这是一个创建Config示例的函数。我们已经见过了一个创建实例函数的规范:像String::new这样的new函数。列表 12-6 中展示了将parse_config转换为一个Config结构体关联函数new的代码:

+
+Filename: src/main.rs +
# use std::env;
+# use std::fs::File;
+# use std::io::prelude::*;
+#
+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...
+
+#     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);
+
+}
+
+# struct Config {
+#     search: String,
+#     filename: String,
+# }
+#
+// ...snip...
+
+impl Config {
+    fn new(args: &[String]) -> Config {
+        let search = args[1].clone();
+        let filename = args[2].clone();
+
+        Config {
+            search: search,
+            filename: filename,
+        }
+    }
+}
+
+
+

Listing 12-6: Changing parse_config into Config::new

+
+
+ +

我们将parse_config的名字改为new并将其移动到impl块中。我们也更新了main中的调用代码。再次尝试编译并确保程序可以运行。

+

从构造函数返回Result

+

这是我们对这个方法最后的重构:还记得当 vector 含有少于三个项时访问索引 1 和 2 会 panic 并给出一个糟糕的错误信息的代码吗?让我们来修改它!列表 12-7 展示了如何在访问这些位置之前检查 slice 是否足够长,并使用一个更好的 panic 信息:

+
+Filename: src/main.rs +
# use std::env;
+# use std::fs::File;
+# use std::io::prelude::*;
+#
+# fn main() {
+#     let args: Vec<String> = env::args().collect();
+#
+#     let config = Config::new(&args);
+#
+#     println!("Searching for {}", config.search);
+#     println!("In file {}", config.filename);
+#
+#     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);
+# }
+#
+# struct Config {
+#     search: String,
+#     filename: String,
+# }
+#
+# impl Config {
+// ...snip...
+fn new(args: &[String]) -> Config {
+    if args.len() < 3 {
+        panic!("not enough arguments");
+    }
+
+    let search = args[1].clone();
+    // ...snip...
+#     let filename = args[2].clone();
+#
+#     Config {
+#         search: search,
+#         filename: filename,
+#     }
+}
+# }
+
+
+

Listing 12-7: Adding a check for the number of arguments

+
+
+ +

通过在new中添加这额外的几行代码,再次尝试不带参数运行程序:

+
$ 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.
+
+

这样就好多了!至少有个一个符合常理的错误信息。然而,还有一堆额外的信息我们并不希望提供给用户。可以通过改变new的签名来完善它。现在它只返回了一个Config,所有没有办法表示创建Config失败的情况。相反,可以如列表 12-8 所示返回一个Result

+
+Filename: src/main.rs +
# use std::env;
+# use std::fs::File;
+# use std::io::prelude::*;
+# 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);
+#
+#     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);
+# }
+# struct Config {
+#     search: String,
+#     filename: String,
+# }
+#
+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,
+        })
+    }
+}
+
+
+

Listing 12-8: Return a Result from Config::new

+
+
+ +
diff --git a/docs/print.html b/docs/print.html index 1b83cc7..147dce0 100644 --- a/docs/print.html +++ b/docs/print.html @@ -6188,7 +6188,379 @@ fn main() { -

记住,程序名称是是第一个参数,所以并不需要args[0]。我们决定从第一个参数将是需要搜索的字符串,所以

+

记住,程序名称是是第一个参数,所以并不需要args[0]。我们决定从第一个参数将是需要搜索的字符串,所以将第一个参数的引用放入变量search中。第二个参数将是文件名,将其放入变量filename中。再次尝试运行程序:

+
$ cargo run test sample.txt
+    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+     Running `target\debug\greprs.exe test sample.txt`
+Searching for test
+In file sample.txt
+
+

很棒!不过有一个问题。让我们不带参数运行:

+
$ cargo run
+    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+     Running `target\debug\greprs.exe`
+thread 'main' panicked at 'index out of bounds: the len is 1
+but the index is 1', ../src/libcollections\vec.rs:1307
+note: Run with `RUST_BACKTRACE=1` for a backtrace.
+
+

因为 vector 中只有一个元素,就是程序名称,不过我们尝试访问第二元素,程序 panic 并提示越界访问。虽然这个错误信息是_准确的_,不过它对程序的用户来说就没有意义了。现在就可以修复这个问题,不过我先继续学习别的内容:在程序结束前我们会改善这个情况。

+

读取文件

+
+

ch12-02-reading-a-file.md +
+commit 4f2dc564851dc04b271a2260c834643dfd86c724

+
+

现在有了一些包含我们需要的信息的变量了,让我们试着使用他们。下一步目标是打开需要搜索的文件。为此,我需要一个文件。在项目的根目录创建一个文件poem.txt,并写入一些艾米莉·狄金森(Emily Dickinson)的诗:

+

Filename: poem.txt

+
I'm nobody! Who are you?
+Are you nobody, too?
+Then there's a pair of us — don't tell!
+They'd banish us, you know.
+
+How dreary to be somebody!
+How public, like a frog
+To tell your name the livelong day
+To an admiring bog!
+
+ +

创建完这个文件后,让我们编辑 src/main.rs 并增加如列表 12-3 所示用来打开文件的代码:

+
+Filename: src/main.rs +
use std::env;
+use std::fs::File;
+use std::io::prelude::*;
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+
+    let search = &args[1];
+    let filename = &args[2];
+
+    println!("Searching for {}", search);
+    println!("In file {}", filename);
+
+    let mut f = File::open(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);
+}
+
+
+

Listing 12-3: Read the contents of the file specified by the second argument

+
+
+ +

这里增加了一些新内容。首先,需要更多的use语句来引入标准库中的相关部分:我们需要std::fs::File来处理文件,而std::io::prelude::*则包含许多对于 I/O 包括文件 I/O 有帮助的 trait。类似于 Rust 有一个通用的 prelude 来自动引入特定内容,std::io也有其自己的 prelude 来引入处理 I/O 时需要的内容。不同于默认的 prelude,必须显式use位于std::io中的 prelude。

+

main中,我们增加了三点内容:第一,我们获取了文件的句柄并使用File::open函数与第二个参数中指定的文件名来打开这个文件。第二,我们在变量contents中创建了一个空的可变的String,接着对文件句柄调用read_to_string并以contents字符串作为参数,contentsread_to_string将会放置它读取到的数据地方。最后,我们打印出了整个文件的内容,这是一个确认目前为止的程序能够工作的方法。

+

尝试运行这些代码,随意指定第一个参数(因为还未实现搜索功能的部分)而将 poem.txt 文件将作为第二个参数:

+
$ cargo run the poem.txt
+    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+     Running `target\debug\greprs.exe the poem.txt`
+Searching for the
+In file poem.txt
+With text:
+I'm nobody! Who are you?
+Are you nobody, too?
+Then there's a pair of us — don't tell!
+They'd banish us, you know.
+
+How dreary to be somebody!
+How public, like a frog
+To tell your name the livelong day
+To an admiring bog!
+
+

好的!我们的代码可以工作了!然而,它还有一些瑕疵。因为程序还很小,这些瑕疵并不是什么大问题,不过随着程序功能的丰富,将会越来越难以用简单的方法修复他们。现在就让我们开始重构而不是等待之后处理。重构在只有少量代码时会显得容易得多。

+

读取文件

+
+

ch12-03-improving-error-handling-and-modularity.md +
+commit 4f2dc564851dc04b271a2260c834643dfd86c724

+
+

为了完善我们程序有四个问题需要修复,而他们都与潜在的错误和程序结构有关。第一个问题是在哪打开文件:我们使用了expect来在打开文件失败时指定一个错误信息,不过这个错误信息只是说“文件不存在”。还有很多打开文件失败的方式,不过我们总是假设是由于缺少文件导致的。例如,文件存在但是没有打开它的权限:这时,我们就打印出了错误不符合事实的错误信息!

+

第二,我们不停的使用expect,这就有点类似我们之前在不传递任何命令行参数时索引会panic!时注意到的问题:这虽然时_可以工作_的,不过这有点没有原则性,而且整个程序中都需要他们,将错误处理都置于一处则会显得好很多。

+

第三个问题是main函数现在处理两个工作:解析参数,并打开文件。对于一个小的函数来说,这不是什么大问题。然而随着程序中的main函数不断增长,main函数中独立的任务也会越来越多。因为一个函数拥有很多职责,它将难以理解、难以测试并难以在不破坏其他部分的情况下做出修改。

+

这也关系到我们的第四个问题:searchfilename是程序中配置性的变量,而像fcontents则用来执行程序逻辑。随着main函数增长,将引入更多的变量到作用域中,而当作用域中有更多的变量,将更难以追踪哪个变量用于什么目的。如果能够将配置型变量组织进一个结构就能使他们的目的更明确了。

+

让我们重新组成程序来解决这些问题。

+

二进制项目的关注分离

+

这类项目组织上的问题在很多相似类型的项目中很常见,所以 Rust 社区开发出一种关注分离的组织模式。这种模式可以用来组织任何用 Rust 构建的二进制项目,所以可以证明应该更早的开始这项重构,以为我们的项目符合这个模式。这个模式看起来像这样:

+
    +
  1. 将程序拆分成 main.rslib.rs
  2. +
  3. 将命令行参数解析逻辑放入 main.rs
  4. +
  5. 将程序逻辑放入 lib.rs
  6. +
  7. main函数的工作是: +
      +
    • 解析参数
    • +
    • 设置所有配置性变量
    • +
    • 调用 lib.rs 中的run函数
    • +
    • 如果run返回错误则处理这个错误
    • +
    +
  8. +
+

好的!老实说这个模式好像还很复杂。这就是关注分离的所有内容:main.rs 负责实际的程序运行,而 lib.rs 处理所有真正的任务逻辑。让我们将程序重构成这种模式。首先,提取出一个目的只在于解析参数的函数。列表 12-4 中展示了一个新的开始,main函数调用了一个新函数parse_config,它仍然定义于 src/main.rs 中:

+
+Filename: src/main.rs +
# use std::env;
+# use std::fs::File;
+# use std::io::prelude::*;
+#
+fn main() {
+    let args: Vec<String> = env::args().collect();
+
+    let (search, filename) = parse_config(&args);
+
+    println!("Searching for {}", search);
+    println!("In file {}", filename);
+
+    // ...snip...
+#
+#     let mut f = File::open(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);
+}
+
+fn parse_config(args: &[String]) -> (&str, &str) {
+    let search = &args[1];
+    let filename = &args[2];
+
+    (search, filename)
+}
+
+
+

Listing 12-4: Extract a parse_config function from main

+
+
+ +

这看起来好像有点复杂,不过我们将一点一点的开展重构。在做出这些改变之后,再次运行程序并验证参数解析是否仍然正常。经常验证你的进展是一个好习惯,这样在遇到问题时就能更好地理解什么修改造成了错误。

+

组合配置值

+

现在我们有了一个函数了,让我们接着完善它。我们代码还能设计的更好一些:函数返回了一个元组,不过接着立刻就解构成了单独的部分。这些代码本身没有问题,不过有一个地方表明仍有改善的余地:我们调用了parse_config方法。函数名中的config部分也表明了返回的两个值应该是组合在一起的,因为他们都是某个配置值的一部分。

+
+

注意:一些同学将当使用符合类型更为合适的时候使用基本类型当作一种称为基本类型偏执primitive obsession)的反模式。

+
+

让我们引入一个结构体来存放所有的配置。列表 12-5 中展示了新增的Config结构体定义、重构后的parse_configmain函数中的相关更新:

+
+Filename: src/main.rs +
# use std::env;
+# use std::fs::File;
+# use std::io::prelude::*;
+#
+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...
+#     let mut contents = String::new();
+#     f.read_to_string(&mut contents).expect("something went wrong reading the file");
+#
+#    println!("With text:\n{}", contents);
+}
+
+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,
+    }
+}
+
+
+

Listing 12-5: Refactoring parse_config to return an instance of a Config +struct

+
+
+ +

parse_config的签名现在表明它返回一个Config值。在parse_config的函数体中,我们之前返回了argsString值引用的字符串 slice,不过Config定义为拥有两个有所有权的String值。因为parse_config的参数是一个String值的 slice,Config实例不能获取String值的所有权:这违反了 Rust 的借用规则,因为main函数中的args变量拥有这些String值并只允许parse_config函数借用他们。

+

还有许多不同的方式可以处理String的数据;现在我们使用简单但低效率的方式,在字符串 slice 上调用clone方法。clone调用会生成一个字符串数据的完整拷贝,而且Config实例可以拥有它,不过这会消耗更多时间和内存来储存拷贝字符串数据的引用,不过拷贝数据让我们使我们的代码显得更加直白。

+ +
+

使用clone权衡取舍

+

由于其运行时消耗,许多 Rustacean 之间有一个趋势是倾向于不使用clone来解决所有权问题。在关于迭代器的第XX章中,我们将会学习如何更有效率的处理这种情况。现在,为了编写我们的程序拷贝一些字符串是没有问题。我们只进行了一次拷贝,而且文件名和要搜索的字符串都比较短。随着你对 Rust 更加熟练,将更轻松的省略这个权衡的步骤,不过现在调用clone是完全可以接受的。

+
+ +

main函数更新为将parse_config返回的Config实例放入变量config中,并将分别使用searchfilename变量的代码更新为使用Config结构体的字段。

+

创建一个Config构造函数

+

现在让我们考虑一下parse_config的目的:这是一个创建Config示例的函数。我们已经见过了一个创建实例函数的规范:像String::new这样的new函数。列表 12-6 中展示了将parse_config转换为一个Config结构体关联函数new的代码:

+
+Filename: src/main.rs +
# use std::env;
+# use std::fs::File;
+# use std::io::prelude::*;
+#
+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...
+
+#     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);
+
+}
+
+# struct Config {
+#     search: String,
+#     filename: String,
+# }
+#
+// ...snip...
+
+impl Config {
+    fn new(args: &[String]) -> Config {
+        let search = args[1].clone();
+        let filename = args[2].clone();
+
+        Config {
+            search: search,
+            filename: filename,
+        }
+    }
+}
+
+
+

Listing 12-6: Changing parse_config into Config::new

+
+
+ +

我们将parse_config的名字改为new并将其移动到impl块中。我们也更新了main中的调用代码。再次尝试编译并确保程序可以运行。

+

从构造函数返回Result

+

这是我们对这个方法最后的重构:还记得当 vector 含有少于三个项时访问索引 1 和 2 会 panic 并给出一个糟糕的错误信息的代码吗?让我们来修改它!列表 12-7 展示了如何在访问这些位置之前检查 slice 是否足够长,并使用一个更好的 panic 信息:

+
+Filename: src/main.rs +
# use std::env;
+# use std::fs::File;
+# use std::io::prelude::*;
+#
+# fn main() {
+#     let args: Vec<String> = env::args().collect();
+#
+#     let config = Config::new(&args);
+#
+#     println!("Searching for {}", config.search);
+#     println!("In file {}", config.filename);
+#
+#     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);
+# }
+#
+# struct Config {
+#     search: String,
+#     filename: String,
+# }
+#
+# impl Config {
+// ...snip...
+fn new(args: &[String]) -> Config {
+    if args.len() < 3 {
+        panic!("not enough arguments");
+    }
+
+    let search = args[1].clone();
+    // ...snip...
+#     let filename = args[2].clone();
+#
+#     Config {
+#         search: search,
+#         filename: filename,
+#     }
+}
+# }
+
+
+

Listing 12-7: Adding a check for the number of arguments

+
+
+ +

通过在new中添加这额外的几行代码,再次尝试不带参数运行程序:

+
$ 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.
+
+

这样就好多了!至少有个一个符合常理的错误信息。然而,还有一堆额外的信息我们并不希望提供给用户。可以通过改变new的签名来完善它。现在它只返回了一个Config,所有没有办法表示创建Config失败的情况。相反,可以如列表 12-8 所示返回一个Result

+
+Filename: src/main.rs +
# use std::env;
+# use std::fs::File;
+# use std::io::prelude::*;
+# 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);
+#
+#     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);
+# }
+# struct Config {
+#     search: String,
+#     filename: String,
+# }
+#
+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,
+        })
+    }
+}
+
+
+

Listing 12-8: Return a Result from Config::new

+
+
+ diff --git a/src/PREFACE.md b/src/PREFACE.md index a78a7bb..e2c0c57 100644 --- a/src/PREFACE.md +++ b/src/PREFACE.md @@ -1 +1,9 @@ -# trpl-zh-cn \ No newline at end of file +# Rust 程序设计语言(第二版) 简体中文版 + +还在施工中... + +## Gitbook 中存在的问题 + +`
`中的 markdown 没有语法高亮QAQ + +[https://github.com/GitbookIO/gitbook/issues/1727](https://github.com/GitbookIO/gitbook/issues/1727) \ No newline at end of file diff --git a/src/ch12-01-accepting-command-line-arguments.md b/src/ch12-01-accepting-command-line-arguments.md index 4da612d..0b8717c 100644 --- a/src/ch12-01-accepting-command-line-arguments.md +++ b/src/ch12-01-accepting-command-line-arguments.md @@ -79,4 +79,25 @@ Listing 12-2: Create variables to hold the search argument and filename argument -记住,程序名称是是第一个参数,所以并不需要`args[0]`。我们决定从第一个参数将是需要搜索的字符串,所以 \ No newline at end of file +记住,程序名称是是第一个参数,所以并不需要`args[0]`。我们决定从第一个参数将是需要搜索的字符串,所以将第一个参数的引用放入变量`search`中。第二个参数将是文件名,将其放入变量`filename`中。再次尝试运行程序: + +``` +$ cargo run test sample.txt + Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs + Running `target\debug\greprs.exe test sample.txt` +Searching for test +In file sample.txt +``` + +很棒!不过有一个问题。让我们不带参数运行: + +``` +$ cargo run + Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs + Running `target\debug\greprs.exe` +thread 'main' panicked at 'index out of bounds: the len is 1 +but the index is 1', ../src/libcollections\vec.rs:1307 +note: Run with `RUST_BACKTRACE=1` for a backtrace. +``` + +因为 vector 中只有一个元素,就是程序名称,不过我们尝试访问第二元素,程序 panic 并提示越界访问。虽然这个错误信息是_准确的_,不过它对程序的用户来说就没有意义了。现在就可以修复这个问题,不过我先继续学习别的内容:在程序结束前我们会改善这个情况。 \ No newline at end of file diff --git a/src/ch12-02-reading-a-file.md b/src/ch12-02-reading-a-file.md index e69de29..860cb7b 100644 --- a/src/ch12-02-reading-a-file.md +++ b/src/ch12-02-reading-a-file.md @@ -0,0 +1,89 @@ +## 读取文件 + +> [ch12-02-reading-a-file.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-02-reading-a-file.md) +>
+> commit 4f2dc564851dc04b271a2260c834643dfd86c724 + +现在有了一些包含我们需要的信息的变量了,让我们试着使用他们。下一步目标是打开需要搜索的文件。为此,我需要一个文件。在项目的根目录创建一个文件`poem.txt`,并写入一些艾米莉·狄金森(Emily Dickinson)的诗: + +Filename: poem.txt + +``` +I'm nobody! Who are you? +Are you nobody, too? +Then there's a pair of us — don't tell! +They'd banish us, you know. + +How dreary to be somebody! +How public, like a frog +To tell your name the livelong day +To an admiring bog! +``` + + + +创建完这个文件后,让我们编辑 *src/main.rs* 并增加如列表 12-3 所示用来打开文件的代码: + +
+Filename: src/main.rs + +```rust +use std::env; +use std::fs::File; +use std::io::prelude::*; + +fn main() { + let args: Vec = env::args().collect(); + + let search = &args[1]; + let filename = &args[2]; + + println!("Searching for {}", search); + println!("In file {}", filename); + + let mut f = File::open(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); +} +``` + +
+ +Listing 12-3: Read the contents of the file specified by the second argument + +
+
+ + + +这里增加了一些新内容。首先,需要更多的`use`语句来引入标准库中的相关部分:我们需要`std::fs::File`来处理文件,而`std::io::prelude::*`则包含许多对于 I/O 包括文件 I/O 有帮助的 trait。类似于 Rust 有一个通用的 prelude 来自动引入特定内容,`std::io`也有其自己的 prelude 来引入处理 I/O 时需要的内容。不同于默认的 prelude,必须显式`use`位于`std::io`中的 prelude。 + +在`main`中,我们增加了三点内容:第一,我们获取了文件的句柄并使用`File::open`函数与第二个参数中指定的文件名来打开这个文件。第二,我们在变量`contents`中创建了一个空的可变的`String`,接着对文件句柄调用`read_to_string`并以`contents`字符串作为参数,`contents`是`read_to_string`将会放置它读取到的数据地方。最后,我们打印出了整个文件的内容,这是一个确认目前为止的程序能够工作的方法。 + +尝试运行这些代码,随意指定第一个参数(因为还未实现搜索功能的部分)而将 *poem.txt* 文件将作为第二个参数: + +``` +$ cargo run the poem.txt + Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs + Running `target\debug\greprs.exe the poem.txt` +Searching for the +In file poem.txt +With text: +I'm nobody! Who are you? +Are you nobody, too? +Then there's a pair of us — don't tell! +They'd banish us, you know. + +How dreary to be somebody! +How public, like a frog +To tell your name the livelong day +To an admiring bog! +``` + +好的!我们的代码可以工作了!然而,它还有一些瑕疵。因为程序还很小,这些瑕疵并不是什么大问题,不过随着程序功能的丰富,将会越来越难以用简单的方法修复他们。现在就让我们开始重构而不是等待之后处理。重构在只有少量代码时会显得容易得多。 \ No newline at end of file diff --git a/src/ch12-03-improving-error-handling-and-modularity.md b/src/ch12-03-improving-error-handling-and-modularity.md index e69de29..bdcf8af 100644 --- a/src/ch12-03-improving-error-handling-and-modularity.md +++ b/src/ch12-03-improving-error-handling-and-modularity.md @@ -0,0 +1,342 @@ +## 读取文件 + +> [ch12-03-improving-error-handling-and-modularity.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-03-improving-error-handling-and-modularity.md) +>
+> commit 4f2dc564851dc04b271a2260c834643dfd86c724 + +为了完善我们程序有四个问题需要修复,而他们都与潜在的错误和程序结构有关。第一个问题是在哪打开文件:我们使用了`expect`来在打开文件失败时指定一个错误信息,不过这个错误信息只是说“文件不存在”。还有很多打开文件失败的方式,不过我们总是假设是由于缺少文件导致的。例如,文件存在但是没有打开它的权限:这时,我们就打印出了错误不符合事实的错误信息! + +第二,我们不停的使用`expect`,这就有点类似我们之前在不传递任何命令行参数时索引会`panic!`时注意到的问题:这虽然时_可以工作_的,不过这有点没有原则性,而且整个程序中都需要他们,将错误处理都置于一处则会显得好很多。 + +第三个问题是`main`函数现在处理两个工作:解析参数,并打开文件。对于一个小的函数来说,这不是什么大问题。然而随着程序中的`main`函数不断增长,`main`函数中独立的任务也会越来越多。因为一个函数拥有很多职责,它将难以理解、难以测试并难以在不破坏其他部分的情况下做出修改。 + +这也关系到我们的第四个问题:`search`和`filename`是程序中配置性的变量,而像`f`和`contents`则用来执行程序逻辑。随着`main`函数增长,将引入更多的变量到作用域中,而当作用域中有更多的变量,将更难以追踪哪个变量用于什么目的。如果能够将配置型变量组织进一个结构就能使他们的目的更明确了。 + +让我们重新组成程序来解决这些问题。 + +### 二进制项目的关注分离 + +这类项目组织上的问题在很多相似类型的项目中很常见,所以 Rust 社区开发出一种关注分离的组织模式。这种模式可以用来组织任何用 Rust 构建的二进制项目,所以可以证明应该更早的开始这项重构,以为我们的项目符合这个模式。这个模式看起来像这样: + +1. 将程序拆分成 *main.rs* 和 *lib.rs*。 +2. 将命令行参数解析逻辑放入 *main.rs*。 +3. 将程序逻辑放入 *lib.rs*。 +4. `main`函数的工作是: + * 解析参数 + * 设置所有配置性变量 + * 调用 *lib.rs* 中的`run`函数 + * 如果`run`返回错误则处理这个错误 + +好的!老实说这个模式好像还很复杂。这就是关注分离的所有内容:*main.rs* 负责实际的程序运行,而 *lib.rs* 处理所有真正的任务逻辑。让我们将程序重构成这种模式。首先,提取出一个目的只在于解析参数的函数。列表 12-4 中展示了一个新的开始,`main`函数调用了一个新函数`parse_config`,它仍然定义于 *src/main.rs* 中: + +
+Filename: src/main.rs + +```rust +# use std::env; +# use std::fs::File; +# use std::io::prelude::*; +# +fn main() { + let args: Vec = env::args().collect(); + + let (search, filename) = parse_config(&args); + + println!("Searching for {}", search); + println!("In file {}", filename); + + // ...snip... +# +# let mut f = File::open(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); +} + +fn parse_config(args: &[String]) -> (&str, &str) { + let search = &args[1]; + let filename = &args[2]; + + (search, filename) +} +``` + +
+ +Listing 12-4: Extract a `parse_config` function from `main` + +
+
+ + + +这看起来好像有点复杂,不过我们将一点一点的开展重构。在做出这些改变之后,再次运行程序并验证参数解析是否仍然正常。经常验证你的进展是一个好习惯,这样在遇到问题时就能更好地理解什么修改造成了错误。 + +### 组合配置值 + +现在我们有了一个函数了,让我们接着完善它。我们代码还能设计的更好一些:函数返回了一个元组,不过接着立刻就解构成了单独的部分。这些代码本身没有问题,不过有一个地方表明仍有改善的余地:我们调用了`parse_config`方法。函数名中的`config`部分也表明了返回的两个值应该是组合在一起的,因为他们都是某个配置值的一部分。 + +> 注意:一些同学将当使用符合类型更为合适的时候使用基本类型当作一种称为**基本类型偏执**(*primitive obsession*)的反模式。 + +让我们引入一个结构体来存放所有的配置。列表 12-5 中展示了新增的`Config`结构体定义、重构后的`parse_config`和`main`函数中的相关更新: + +
+Filename: src/main.rs + +```rust +# use std::env; +# use std::fs::File; +# use std::io::prelude::*; +# +fn main() { + let args: Vec = 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... +# let mut contents = String::new(); +# f.read_to_string(&mut contents).expect("something went wrong reading the file"); +# +# println!("With text:\n{}", contents); +} + +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, + } +} +``` + +
+ +Listing 12-5: Refactoring `parse_config` to return an instance of a `Config` +struct + +
+
+ + + +`parse_config`的签名现在表明它返回一个`Config`值。在`parse_config`的函数体中,我们之前返回了`args`中`String`值引用的字符串 slice,不过`Config`定义为拥有两个有所有权的`String`值。因为`parse_config`的参数是一个`String`值的 slice,`Config`实例不能获取`String`值的所有权:这违反了 Rust 的借用规则,因为`main`函数中的`args`变量拥有这些`String`值并只允许`parse_config`函数借用他们。 + +还有许多不同的方式可以处理`String`的数据;现在我们使用简单但低效率的方式,在字符串 slice 上调用`clone`方法。`clone`调用会生成一个字符串数据的完整拷贝,而且`Config`实例可以拥有它,不过这会消耗更多时间和内存来储存拷贝字符串数据的引用,不过拷贝数据让我们使我们的代码显得更加直白。 + + + +> #### 使用`clone`权衡取舍 +> +> 由于其运行时消耗,许多 Rustacean 之间有一个趋势是倾向于不使用`clone`来解决所有权问题。在关于迭代器的第XX章中,我们将会学习如何更有效率的处理这种情况。现在,为了编写我们的程序拷贝一些字符串是没有问题。我们只进行了一次拷贝,而且文件名和要搜索的字符串都比较短。随着你对 Rust 更加熟练,将更轻松的省略这个权衡的步骤,不过现在调用`clone`是完全可以接受的。 + + + +`main`函数更新为将`parse_config`返回的`Config`实例放入变量`config`中,并将分别使用`search`和`filename`变量的代码更新为使用`Config`结构体的字段。 + +### 创建一个`Config`构造函数 + +现在让我们考虑一下`parse_config`的目的:这是一个创建`Config`示例的函数。我们已经见过了一个创建实例函数的规范:像`String::new`这样的`new`函数。列表 12-6 中展示了将`parse_config`转换为一个`Config`结构体关联函数`new`的代码: + +
+Filename: src/main.rs + +```rust +# use std::env; +# use std::fs::File; +# use std::io::prelude::*; +# +fn main() { + let args: Vec = env::args().collect(); + + let config = Config::new(&args); + + println!("Searching for {}", config.search); + println!("In file {}", config.filename); + + // ...snip... + +# 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); + +} + +# struct Config { +# search: String, +# filename: String, +# } +# +// ...snip... + +impl Config { + fn new(args: &[String]) -> Config { + let search = args[1].clone(); + let filename = args[2].clone(); + + Config { + search: search, + filename: filename, + } + } +} +``` + +
+ +Listing 12-6: Changing `parse_config` into `Config::new` + +
+
+ + + +我们将`parse_config`的名字改为`new`并将其移动到`impl`块中。我们也更新了`main`中的调用代码。再次尝试编译并确保程序可以运行。 + +### 从构造函数返回`Result` + +这是我们对这个方法最后的重构:还记得当 vector 含有少于三个项时访问索引 1 和 2 会 panic 并给出一个糟糕的错误信息的代码吗?让我们来修改它!列表 12-7 展示了如何在访问这些位置之前检查 slice 是否足够长,并使用一个更好的 panic 信息: + +
+Filename: src/main.rs + +```rust +# use std::env; +# use std::fs::File; +# use std::io::prelude::*; +# +# fn main() { +# let args: Vec = env::args().collect(); +# +# let config = Config::new(&args); +# +# println!("Searching for {}", config.search); +# println!("In file {}", config.filename); +# +# 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); +# } +# +# struct Config { +# search: String, +# filename: String, +# } +# +# impl Config { +// ...snip... +fn new(args: &[String]) -> Config { + if args.len() < 3 { + panic!("not enough arguments"); + } + + let search = args[1].clone(); + // ...snip... +# let filename = args[2].clone(); +# +# Config { +# search: search, +# filename: filename, +# } +} +# } +``` + +
+ +Listing 12-7: Adding a check for the number of arguments + +
+
+ + + +通过在`new`中添加这额外的几行代码,再次尝试不带参数运行程序: + +``` +$ 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. +``` + +这样就好多了!至少有个一个符合常理的错误信息。然而,还有一堆额外的信息我们并不希望提供给用户。可以通过改变`new`的签名来完善它。现在它只返回了一个`Config`,所有没有办法表示创建`Config`失败的情况。相反,可以如列表 12-8 所示返回一个`Result`: + +
+Filename: src/main.rs + +```rust +# use std::env; +# use std::fs::File; +# use std::io::prelude::*; +# use std::process; +# +# fn main() { +# let args: Vec = 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); +# +# 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); +# } +# struct Config { +# search: String, +# filename: String, +# } +# +impl Config { + fn new(args: &[String]) -> Result { + 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, + }) + } +} +``` + +
+ +Listing 12-8: Return a `Result` from `Config::new` + +
+
+ + +