diff --git a/README.md b/README.md index c8965ee..16d2525 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Rust 程序设计语言(第二版) 简体中文版 -还在施工中:目前翻译到第十六章 +还在施工中:目前翻译到第十六章,正在更新第十二章 -目前官方进度:[第十六章](https://github.com/rust-lang/book/projects/1)(17~20 章还在编写当中) +目前官方进度:[第十七章](https://github.com/rust-lang/book/projects/1)(18~20 章还在编写当中) GitBook 代码排版已大体解决,已不影响阅读 \ No newline at end of file diff --git a/docs/ch01-00-introduction.html b/docs/ch01-00-introduction.html index 8fbe464..ffb2dd1 100644 --- a/docs/ch01-00-introduction.html +++ b/docs/ch01-00-introduction.html @@ -47,7 +47,7 @@
@@ -71,14 +71,14 @@

ch01-00-introduction.md
-commit 4f2dc564851dc04b271a2260c834643dfd86c724

+commit 62f78bb3f7c222b574ff547d0161c2533691f9b4

欢迎阅读“Rust 程序设计语言”,一本关于 Rust 的介绍性书籍。Rust 是一个着用于安全、速度和并发的编程语言。它的设计不仅可以使程序获得性能和对底层语言的控制,并且能够享受高级语言强大的抽象能力。这些特性使得 Rust 适合那些有类似 C 语言经验并正在寻找一个更安全的替代者的程序员,同时也适合那些来自类似 Python 语言背景,正在探索在不牺牲表现力的情况下编写更好性能代码的开发者。

Rust 在编译时进行其绝大多数的安全检查和内存管理决策,因此程序的运行时性能没有受到影响。这让其在许多其他语言不擅长的应用场景中得以大显身手:存在可预测空间和时间要求的程序,嵌入到其他语言中,以及编写底层代码,如设备驱动和操作系统。Rust 也很擅长 web 程序:它驱动着 Rust 包注册网站(package registry site),crates.io!我们期待看到使用 Rust 进行创作。

本书是为已经至少了解一门编程语言的读者而写的。读完本书之后,你应该能自如的编写 Rust 程序。我们将通过小而集中并且相互依赖的例子来学习 Rust,并向你展示如何使用 Rust 多样的功能,同时了解它们在幕后是如何执行的。

为本书做出贡献

-

本书是开源的。如果你发现任何错误,请不要犹豫,在 GitHub 上发起 issue 或提交 pull request。

+

本书是开源的。如果你发现任何错误,请不要犹豫,在 GitHub 上发起 issue 或提交 pull request。请查看CONTRIBUTING.md获取更多信息。

译者注:这是本译本的 GitHub 仓库,同样欢迎 Issue 和 PR :)

diff --git a/docs/ch01-01-installation.html b/docs/ch01-01-installation.html index 01d3a34..f4f433c 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 18155d8..0cd336d 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 29feb75..8282c1d 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 215d1ea..b1c7798 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 72c49f5..de2acc8 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 47f40e9..214e29a 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 a96643a..eb08517 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 a76a497..9816297 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 5ae5344..b47c27a 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 30f9aa0..0d203a4 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 c5d7206..b324089 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 2785bf7..5b8bac2 100644 --- a/docs/ch04-02-references-and-borrowing.html +++ b/docs/ch04-02-references-and-borrowing.html @@ -47,7 +47,7 @@
@@ -71,7 +71,7 @@

ch04-02-references-and-borrowing.md
-commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

+commit 5e0546f53cce14b126527d9ba6d1b8eb212b4f3d

在上一部分的结尾处的使用元组的代码是有问题的,我们需要将String返回给调用者函数这样就可以在调用calculate_length后仍然可以使用String了,因为String先被移动到了calculate_length

下面是如何定义并使用一个(新的)calculate_length函数,它以一个对象的引用作为参数而不是获取值的所有权:

@@ -243,7 +243,7 @@ for it to be borrowed from. // Danger!

因为s是在dangle创建的,当dangle的代码执行完毕后,s将被释放。不过我们尝试返回一个它的引用。这意味着这个引用会指向一个无效的String!这可不好。Rust 不会允许我们这么做的。

-

正确的代码是直接返回String

+

这里的解决方法是直接返回String

fn no_dangle() -> String {
     let s = String::from("hello");
 
diff --git a/docs/ch04-03-slices.html b/docs/ch04-03-slices.html
index 2be23b4..250ce27 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 1a24889..0064797 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 85bac45..53baf5a 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 de1b336..524c9d6 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 0e443c3..dc79f68 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 0c482b1..4ef56a3 100644 --- a/docs/ch06-02-match.html +++ b/docs/ch06-02-match.html @@ -47,7 +47,7 @@
@@ -71,9 +71,9 @@

ch06-02-match.md
-commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

+commit 64090418c23d615facfe49a8d548ad9baea6b097

-

Rust 有一个叫做match的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会讲到所有不同种类的模式以及他们的作用。match的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。

+

Rust 有一个叫做match的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及他们的作用。match的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。

match表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会检查match的每一个模式,并且在遇到第一个“符合”的模式时,值会进入相关联的代码块并在执行中被使用。

因为刚刚提到了硬币,让我们用他们来作为一个使用match的例子!我们可以编写一个函数来获取一个未知的(美国)硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如列表 6-3 中所示:

enum Coin {
diff --git a/docs/ch06-03-if-let.html b/docs/ch06-03-if-let.html
index 30c0605..43874eb 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 e087ded..a49fcd9 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 8179bc0..c1e14bf 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 0b687a3..6f70929 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 201ebea..a35ffb4 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 229f818..90d5a08 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 1517d30..b97d598 100644 --- a/docs/ch08-01-vectors.html +++ b/docs/ch08-01-vectors.html @@ -47,7 +47,7 @@
@@ -71,7 +71,7 @@

ch08-01-vectors.md
-commit 4f2dc564851dc04b271a2260c834643dfd86c724

+commit 6c24544ba718bce0755bdaf03423af86280051d5

我们要讲到的第一个类型是Vec<T>,也被称为 vector。vector 允许我们在一个单独的数据结构中储存多于一个值,它在内存中彼此相邻的排列所有的值。vector 只能储存相同类型的值。他们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。

新建 vector

@@ -159,7 +159,9 @@ let row = vec![ SpreadsheetCell::Float(10.12), ];
-

Rust 在编译时就必须准确的知道 vector 中类型的原因是它需要知道储存每个元素到底需要多少内存。第二个优点是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加match意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。

+

Listing 8-1: Defining an enum to be able to hold +different types of data in a vector

+

Rust 在编译时就必须准确的知道 vector 中类型的原因是它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加match意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。

如果在编写程序时不能确切无遗的知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。相反,你可以使用 trait 对象,第十七章会讲到它。

现在我们了解了一些使用 vector 的最常见的方式,请一定去看看标准库中Vec定义的很多其他实用方法的 API 文档。例如,除了push之外还有一个pop方法,它会移除并返回 vector 的最后一个元素。让我们继续下一个集合类型:String

diff --git a/docs/ch08-02-strings.html b/docs/ch08-02-strings.html index 010c60b..dbc8ac8 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 f1492c8..02acedf 100644 --- a/docs/ch08-03-hash-maps.html +++ b/docs/ch08-03-hash-maps.html @@ -47,7 +47,7 @@
diff --git a/docs/ch09-00-error-handling.html b/docs/ch09-00-error-handling.html index ede643f..29bdedf 100644 --- a/docs/ch09-00-error-handling.html +++ b/docs/ch09-00-error-handling.html @@ -47,7 +47,7 @@
diff --git a/docs/ch09-01-unrecoverable-errors-with-panic.html b/docs/ch09-01-unrecoverable-errors-with-panic.html index 58b36e1..b5f940f 100644 --- a/docs/ch09-01-unrecoverable-errors-with-panic.html +++ b/docs/ch09-01-unrecoverable-errors-with-panic.html @@ -47,7 +47,7 @@
@@ -71,7 +71,7 @@

ch09-01-unrecoverable-errors-with-panic.md
-commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

+commit e26bb338ab14b98a850c3464e821d54940a45672

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

@@ -124,28 +124,40 @@ error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
$ 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
+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:1392
 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
+   1:     0x560ed90ec04c - std::sys::imp::backtrace::tracing::imp::write::hf33ae72d0baa11ed
+                        at /stable-dist-rustc/build/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:42
+   2:     0x560ed90ee03e - std::panicking::default_hook::{{closure}}::h59672b733cc6a455
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:351
+   3:     0x560ed90edc44 - std::panicking::default_hook::h1670459d2f3f8843
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:367
+   4:     0x560ed90ee41b - std::panicking::rust_panic_with_hook::hcf0ddb069e7abcd7
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:555
+   5:     0x560ed90ee2b4 - std::panicking::begin_panic::hd6eb68e27bdf6140
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:517
+   6:     0x560ed90ee1d9 - std::panicking::begin_panic_fmt::abcd5965948b877f8
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:501
+   7:     0x560ed90ee167 - rust_begin_unwind
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:477
+   8:     0x560ed911401d - core::panicking::panic_fmt::hc0f6d7b2c300cdd9
+                        at /stable-dist-rustc/build/src/libcore/panicking.rs:69
+   9:     0x560ed9113fc8 - core::panicking::panic_bounds_check::h02a4af86d01b3e96
+                        at /stable-dist-rustc/build/src/libcore/panicking.rs:56
+  10:     0x560ed90e71c5 - <collections::vec::Vec<T> as core::ops::Index<usize>>::index::h98abcd4e2a74c41
+                        at /stable-dist-rustc/build/src/libcollections/vec.rs:1392
+  11:     0x560ed90e727a - panic::main::h5d6b77c20526bc35
+                        at /home/you/projects/panic/src/main.rs:4
+  12:     0x560ed90f5d6a - __rust_maybe_catch_panic
+                        at /stable-dist-rustc/build/src/libpanic_unwind/lib.rs:98
+  13:     0x560ed90ee926 - std::rt::lang_start::hd7c880a37a646e81
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:436
+                        at /stable-dist-rustc/build/src/libstd/panic.rs:361
+                        at /stable-dist-rustc/build/src/libstd/rt.rs:57
+  14:     0x560ed90e7302 - main
+  15:     0x7f0d53f16400 - __libc_start_main
+  16:     0x560ed90e6659 - _start
+  17:                0x0 - <unknown>
 

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

diff --git a/docs/ch09-02-recoverable-errors-with-result.html b/docs/ch09-02-recoverable-errors-with-result.html index 34341be..1b03969 100644 --- a/docs/ch09-02-recoverable-errors-with-result.html +++ b/docs/ch09-02-recoverable-errors-with-result.html @@ -47,7 +47,7 @@
diff --git a/docs/ch09-03-to-panic-or-not-to-panic.html b/docs/ch09-03-to-panic-or-not-to-panic.html index 191ff77..8a60eb1 100644 --- a/docs/ch09-03-to-panic-or-not-to-panic.html +++ b/docs/ch09-03-to-panic-or-not-to-panic.html @@ -47,7 +47,7 @@
diff --git a/docs/ch10-00-generics.html b/docs/ch10-00-generics.html index 2eb3aab..b715250 100644 --- a/docs/ch10-00-generics.html +++ b/docs/ch10-00-generics.html @@ -47,7 +47,7 @@
diff --git a/docs/ch10-01-syntax.html b/docs/ch10-01-syntax.html index eee26e4..a02c508 100644 --- a/docs/ch10-01-syntax.html +++ b/docs/ch10-01-syntax.html @@ -47,7 +47,7 @@
diff --git a/docs/ch10-02-traits.html b/docs/ch10-02-traits.html index 1799486..219cfe8 100644 --- a/docs/ch10-02-traits.html +++ b/docs/ch10-02-traits.html @@ -47,7 +47,7 @@
@@ -71,7 +71,7 @@

ch10-02-traits.md
-commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

+commit e5a987f5da3fba24e55f5c7102ec63f9dc3bc360

trait 允许我们进行另一种抽象:他们让我们可以抽象类型所通用的行为。trait 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。在使用泛型类型参数的场景中,可以使用 trait bounds 在编译时指定泛型可以是任何实现了某个 trait 的类型,并由此在这个场景下拥有我们希望的功能。

@@ -267,7 +267,7 @@ error[E0507]: cannot move out of borrowed content | cannot move out of borrowed content

错误的核心是cannot move out of type [T], a non-copy array,对于非泛型版本的largest函数,我们只尝试了寻找最大的i32char。正如第四章讨论过的,像i32char这样的类型是已知大小的并可以储存在栈上,所以他们实现了Copy trait。当我们将largest函数改成使用泛型后,现在list参数的类型就有可能是没有实现Copy trait 的,这意味着我们可能不能将list[0]的值移动到largest变量中。

-

如果只想对实现了Copy的类型调用这些带啊吗,可以在T的 trait bounds 中增加Copy!列表 10-15 中展示了一个可以编译的泛型版本的largest函数的完整代码,只要传递给largest的 slice 值的类型实现了PartialOrdCopy这两个 trait,例如i32char

+

如果只想对实现了Copy的类型调用这些代码,可以在T的 trait bounds 中增加Copy!列表 10-15 中展示了一个可以编译的泛型版本的largest函数的完整代码,只要传递给largest的 slice 值的类型实现了PartialOrdCopy这两个 trait,例如i32char

Filename: src/main.rs

use std::cmp::PartialOrd;
 
diff --git a/docs/ch10-03-lifetime-syntax.html b/docs/ch10-03-lifetime-syntax.html
index e9ddf55..89ec0c2 100644
--- a/docs/ch10-03-lifetime-syntax.html
+++ b/docs/ch10-03-lifetime-syntax.html
@@ -47,7 +47,7 @@
         
 
         
 
         
diff --git a/docs/ch11-00-testing.html b/docs/ch11-00-testing.html index c4f81f3..6e46f25 100644 --- a/docs/ch11-00-testing.html +++ b/docs/ch11-00-testing.html @@ -47,7 +47,7 @@
diff --git a/docs/ch11-01-writing-tests.html b/docs/ch11-01-writing-tests.html index 11785ec..c5284e4 100644 --- a/docs/ch11-01-writing-tests.html +++ b/docs/ch11-01-writing-tests.html @@ -47,7 +47,7 @@
diff --git a/docs/ch11-02-running-tests.html b/docs/ch11-02-running-tests.html index acfd72c..e4edde1 100644 --- a/docs/ch11-02-running-tests.html +++ b/docs/ch11-02-running-tests.html @@ -47,7 +47,7 @@
diff --git a/docs/ch11-03-test-organization.html b/docs/ch11-03-test-organization.html index e8d2ddc..25265b7 100644 --- a/docs/ch11-03-test-organization.html +++ b/docs/ch11-03-test-organization.html @@ -47,7 +47,7 @@
diff --git a/docs/ch12-00-an-io-project.html b/docs/ch12-00-an-io-project.html index cae0e54..27045c0 100644 --- a/docs/ch12-00-an-io-project.html +++ b/docs/ch12-00-an-io-project.html @@ -47,7 +47,7 @@
@@ -67,22 +67,34 @@
-

一个 I/O 项目

+

一个 I/O 项目:构建一个小巧的 grep

ch12-00-an-io-project.md
-commit 4f2dc564851dc04b271a2260c834643dfd86c724

+commit 1f432fc231cfbc310433ab2a354d77058444288c

-

之前几个章节我们学习了很多知识。让我们一起运用这些新知识来构建一个项目。在这个过程中,我们还将学习到更多 Rust 标准库的内容。

-

那么我们应该写点什么呢?这得是一个利用 Rust 优势的项目。Rust 的一个强大的用途是命令行工具:Rust 的运行速度、安全性、“单二进制文件”输出和跨平台支持使得它称为这类工作的绝佳选择。所以我们将创建一个我们自己的经典命令行工具:grepgrep有着极为简单的应用场景,它完成如下工作:

-
    -
  1. 它获取一个文件和一个字符串作为参数。
  2. + + +

    本章既是一个目前所学的很多技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。

    +

    Rust 的运行速度、安全性、“单二进制文件”输出和跨平台支持使其成为创建命令行程序的绝佳选择,所以我们的项目将创建一个我们自己版本的经典命令行工具:grep。grep 是“Globally search a Regular Expression and Print.”的首字母缩写。grep最简单的使用场景是使用如下步骤在特定文件中搜索指定字符串:

    +
      +
    • 获取一个文件和一个字符串作为参数。
    • 读取文件
    • 寻找文件中包含字符串参数的行
    • 打印出这些行
    • -
-

另外,我们还将添加一个额外的功能:一个环境变量允许我们大小写不敏感的搜索字符串参数。

-

还有另一个很好的理由使用grep作为示例项目:Rust 社区的成员,Andrew Gallant,已经使用 Rust 创建了一个功能非常完整的grep版本。它叫做ripgrep,并且它非常非常快。这样虽然我们的grep将会非常简单,你也会掌握阅读现真实项目的基础知识。

+ +

我们还会展示如何使用环境变量和打印到标准错误而不是标准输出;这些功能在命令行工具中是很常用的。

+

一位 Rust 社区的成员,Andrew Gallant,已经创建了一个功能完整且非常快速的grep版本,叫做ripgrep。相比之下,我们的grep将非常简单,本章将交给你一些帮助你理解像ripgrep这样真实项目的背景知识。

这个项目将会结合之前所学的一些内容:

  • 代码组织(使用第七章学习的模块)
  • @@ -91,13 +103,12 @@ commit 4f2dc564851dc04b271a2260c834643dfd86c724

  • 合理的使用 trait 和生命周期(第十章)
  • 测试(第十一章)
-

另外,我还会简要的讲到闭包、迭代器和 trait 对象,他们分别会在第XX、YY和ZZ章详细介绍。

-

让我们一如既往的使用cargo new创建一个新项目:

-
$ cargo new --bin greprs
+

另外,我还会简要的讲到闭包、迭代器和 trait 对象,他们分别会在第十三章和第十七章中详细介绍。

+

让我们一如既往的使用cargo new创建一个新项目。我们称之为greprs以便与可能已经安装在系统上的grep工具相区别:

+
$ cargo new --bin greprs
      Created binary (application) `greprs` project
 $ cd greprs
 
-

我们版本的grep的叫做“greprs”,这样就不会迷惑用户让他们以为这就是可能已经在系统上安装了功能更完整的grep

diff --git a/docs/ch12-01-accepting-command-line-arguments.html b/docs/ch12-01-accepting-command-line-arguments.html index aef7fee..373af1c 100644 --- a/docs/ch12-01-accepting-command-line-arguments.html +++ b/docs/ch12-01-accepting-command-line-arguments.html @@ -47,7 +47,7 @@
@@ -71,15 +71,31 @@

ch12-01-accepting-command-line-arguments.md
-commit c49e5ee8859f8eb8f8867cbeafbdf5b802aa5894

+commit b8e4fcbf289b82c12121b282747ce05180afb1fb

-

第一个任务是让greprs接受两个命令行参数。crates.io 上有一些现存的库可以帮助我们,不过因为我们正在学习,我们将自己实现一个。

-

我们需要调用一个 Rust 标准库提供的函数:std::env::args。这个函数返回一个传递给程序的命令行参数的迭代器iterator)。我们还未讨论到迭代器,第十三章会全面的介绍他们。但是对于我们的目的来说,使用他们并不需要知道多少技术细节。我们只需要明白两点:

+

第一个任务是让greprs能够接受两个命令行参数:文件名和要搜索的字符串。也就是说希望能够使用cargo run,要搜索的字符串和被搜索的文件的路径来运行程序,像这样:

+
$ cargo run searchstring example-filename.txt
+
+

现在cargo new生成的程序忽略任何传递给它的参数。crates.io 上有一些现存的可以帮助我们接受命令行参数的库,不过因为我们正在学习,让我们实现一个。

+ + +

读取参数值

+

为了能够获取传递给程序的命令行参数的值,我们需要调用一个 Rust 标准库提供的函数:std::env::args。这个函数返回一个传递给程序的命令行参数的迭代器iterator)。我们还未讨论到迭代器,第十三章会全面的介绍他们。但是对于我们现在的目的来说只需要明白两点:

  1. 迭代器生成一系列的值。
  2. 在迭代器上调用collect方法可以将其生成的元素转换为一个 vector。
-

让我们试试列表 12-1 中的代码:

+

让我们尝试一下:使用列表 12-1 中的代码来读取任何传递给greprs的命令行参数并将其收集到一个 vector 中。

+ +

Filename: src/main.rs

use std::env;
 
@@ -88,11 +104,20 @@ fn main() {
     println!("{:?}", args);
 }
 
-

Listing 12-1: Collect the command line arguments into a -vector and print them out

+

Listing 12-1: Collect the command line arguments into a vector and print them +out

-

首先使用use语句来将std::env模块引入作用域。当函数嵌套了多于一层模块时,比如说std::env::args,通常使用use将父模块引入作用域,而不是引入其本身。env::args比单独的args要明确一些。当然,如果使用了多于一个std::env中的函数时,我们也只需要一个use语句。

-

main函数的第一行,我们调用了env::args,并立即使用collect来创建了一个 vector。这里我们也显式的注明了args的类型:collect可以被用来创建很多类型的集合。Rust 并不能推断出我们需要什么类型,所以类型注解是必须的。在 Rust 中我们很少会需要注明类型,不过collect是就一个通常需要这么做的函数。

+

首先使用use语句来将std::env模块引入作用域以便可以使用它的args函数。注意std::env::args函数嵌套进了两层模块中。如第七章讲到的,当所需函数嵌套了多于一层模块时,通常将父模块引入作用域,而不是其自身。这便于我们利用std::env中的其他函数。这比增加了use std::env::args;后仅仅使用args调用函数要更明确一些;这样看起来好像一个定义于当前模块的函数。

+ + +
+

注意:std::env::args在其任何参数包含无效 Unicode 字符时会 panic。如果你需要接受包含无效 Unicode 字符的参数,使用std::env::args_os代替。这个函数返回OsString值而不是String值。出于简单考虑这里使用std::env::args,因为OsString值每个平台都不一样而且比String值处理起来更复杂。

+
+ + + +

main函数的第一行,我们调用了env::args,并立即使用collect来创建了一个包含迭代器所有值的 vector。collect可以被用来创建很多类型的集合,所以这里显式注明的args类型来指定我们需要一个字符串 vector。虽然在 Rust 中我们很少会需要注明类型,collect就是一个经常需要注明类型的函数,因为 Rust 不能推断出你想要什么类型的集合。

最后,我们使用调试格式:?打印出 vector。让我们尝试不用参数运行代码,接着用两个参数:

$ cargo run
 ["target/debug/greprs"]
@@ -101,40 +126,47 @@ $ cargo run needle haystack
 ...snip...
 ["target/debug/greprs", "needle", "haystack"]
 
-

你会注意一个有趣的事情:二进制文件的名字是第一个参数。其原因超出了本章介绍的范围,不过这是我们必须记住的。

-

现在我们有了一个访问所有参数的方法,让我们如列表 12-2 中所示将需要的变量存放到变量中:

+ + +

你可能注意到了 vector 的第一个值是"target/debug/greprs",它是二进制我呢见的名称。其原因超出了本章介绍的范围,不过需要记住的是我们保存了所需的两个参数。

+

将参数值保存进变量

+

打印出参数 vector 中的值仅仅展示了可以访问程序中指定为命令行参数的值。但是这并不是我们想要做的,我们希望将这两个参数的值保存进变量这样就可以在程序使用这些值。让我们如列表 12-2 这样做:

+ +

Filename: src/main.rs

-
use std::env;
+
use std::env;
 
 fn main() {
     let args: Vec<String> = env::args().collect();
 
-    let search = &args[1];
+    let query = &args[1];
     let filename = &args[2];
 
-    println!("Searching for {}", search);
+    println!("Searching for {}", query);
     println!("In file {}", filename);
 }
 
-

Listing 12-2: Create variables to hold the search -argument and filename argument

+

Listing 12-2: Create variables to hold the query argument and filename argument

-

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

+

正如我们在打印出 vector 时所看到的,程序的名称占据了 vector 的第一个值args[0],所以我们从索引1开始。第一个参数greprs是需要搜索的字符串,所以将其将第一个参数的引用存放在变量query中。第二个参数将是文件名,所以将第二个参数的引用放入变量filename中。

+

我们将临时打印出出这些变量的值,再一次证明代码如我们期望的那样工作。让我们使用参数testsample.txt再次运行这个程序:

$ cargo run test sample.txt
     Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
-     Running `target\debug\greprs.exe test sample.txt`
+     Running `target/debug/greprs 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 2bcb988..ac9056e 100644 --- a/docs/ch12-02-reading-a-file.html +++ b/docs/ch12-02-reading-a-file.html @@ -47,7 +47,7 @@
@@ -71,11 +71,11 @@

ch12-02-reading-a-file.md
-commit c49e5ee8859f8eb8f8867cbeafbdf5b802aa5894

+commit b8e4fcbf289b82c12121b282747ce05180afb1fb

-

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

+

接下来我们将读取由命令行文件名参数指定的文件。首先,需要一个用来测试的示例文件——用来确保greprs正常工作的最好的文件是拥有少量文本和多个行且有一些重复单词的文件。列表 12-3 是一首艾米莉·狄金森(Emily Dickinson)的诗,它正适合这个工作!在项目根目录创建一个文件poem.txt,并输入诗 "I'm nobody! Who are you?":

Filename: poem.txt

-
I'm nobody! Who are you?
+
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.
@@ -85,23 +85,27 @@ How public, like a frog
 To tell your name the livelong day
 To an admiring bog!
 
+

Listing 12-3: The poem "I'm nobody! Who are you?" by +Emily Dickinson that will make a good test case

-

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

+ + +

创建完这个文件之后,修改 src/main.rs 并增加如列表 12-4 所示的打开文件的代码:

Filename: src/main.rs

-
use std::env;
+
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 query = &args[1];
     let filename = &args[2];
 
-    println!("Searching for {}", search);
+    println!("Searching for {}", query);
     println!("In file {}", filename);
 
     let mut f = File::open(filename).expect("file not found");
@@ -112,15 +116,15 @@ fn main() {
     println!("With text:\n{}", contents);
 }
 
-

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

+

Listing 12-4: Reading 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 文件将作为第二个参数:

+

首先,增加了更多的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中,我们增加了三点内容:第一,通过传递变量filename的值调用File::open函数的值来获取文件的可变句柄。创建了叫做contents的变量并将其设置为一个可变的,空的String。它将会存放之后读取的文件的内容。第三,对文件句柄调用read_to_string并传递contents的可变引用作为参数。

+

在这些代码之后,我们再次增加了临时的println!打印出读取文件后contents的值,这样就可以检查目前为止的程序能否工作。

+

尝试运行这些代码,随意指定一个字符串作为第一个命令行参数(因为还未实现搜索功能的部分)而将 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`
+     Running `target/debug/greprs the poem.txt`
 Searching for the
 In file poem.txt
 With text:
@@ -134,7 +138,7 @@ How public, like a frog
 To tell your name the livelong day
 To an admiring bog!
 
-

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

+

好的!代码读取并打印出了文件的内容。虽然它还有一些瑕疵:main函数有着多个功能,同时也没有处理可能出现的错误。虽然我们的程序还很小,这些瑕疵并不是什么大问题。不过随着程序功能的丰富,将会越来越难以用简单的方法修复他们。在开发程序时,及早开始重构是一个最佳实践,因为重构少量代码时要容易的多,所以让我们现在就开始吧。

diff --git a/docs/ch12-03-improving-error-handling-and-modularity.html b/docs/ch12-03-improving-error-handling-and-modularity.html index 2e52d2b..1c06d81 100644 --- a/docs/ch12-03-improving-error-handling-and-modularity.html +++ b/docs/ch12-03-improving-error-handling-and-modularity.html @@ -47,7 +47,7 @@
@@ -71,13 +71,14 @@

ch12-03-improving-error-handling-and-modularity.md
-commit bdab3f38da5b7bf7277bfe21ec59a7a81880e6b4

+commit b8e4fcbf289b82c12121b282747ce05180afb1fb

-

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

-

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

-

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

-

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

-

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

+

为了改善我们的程序这里有四个问题需要修复,而且他们都与程序的组织方式和如何处理潜在错误有关。

+

第一,main现在进行了两个任务:它解析了参数并打开了文件。对于一个这样的小函数,这并不是一个大问题。然而如果main中的功能持续增加,main函数处理的单独的任务也会增加。当函数承担了更多责任,它就更难以推导,更难以测试,并且更难以在不破坏其他部分的情况下做出修改。最好能分离出功能这样每个函数就负责一个任务。

+

这同时也关系到第二个问题:searchfilename是程序中的配置变量,而像fcontents则用来执行程序逻辑。随着main函数的增长,就需要引入更多的变量到作用域中,而当作用域中有更多的变量时,将更难以追踪每个变量的目的。最好能将将配置变量组织进一个结构这样就能使他们的目的更明确了。

+

第三个问题是如果打开文件失败我们使用expect来打印出错误信息,不过这个错误信息只是说file not found。除了缺少文件之外还有很多打开文件可能失败的方式:例如,文件可能存在,不过可能没有打开它的权限。如果我们现在就出于这种情况,打印出的file not found错误信息就给了用户一个不符合事实的建议!

+

第四,我们不停的使用expect来处理不同的错误,如果用户没有指定足够的参数来运行程序,他们会从 Rust 得到 "index out of bounds" 错误而这并不能明确的解释问题。如果所有的错误处理都位于一处这样将来的维护者在需要修改错误处理逻辑时就只需要咨询一处代码。将所有的错误处理都放在一处也有助于确保我们打印的错误信息对终端用户来说是有意义的。

+

让我们通过重构项目来解决这些问题。

二进制项目的关注分离

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

    diff --git a/docs/ch12-04-testing-the-librarys-functionality.html b/docs/ch12-04-testing-the-librarys-functionality.html index cf602d6..965c3ac 100644 --- a/docs/ch12-04-testing-the-librarys-functionality.html +++ b/docs/ch12-04-testing-the-librarys-functionality.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch12-05-working-with-environment-variables.html b/docs/ch12-05-working-with-environment-variables.html index 98ecf88..9b60371 100644 --- a/docs/ch12-05-working-with-environment-variables.html +++ b/docs/ch12-05-working-with-environment-variables.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch12-06-writing-to-stderr-instead-of-stdout.html b/docs/ch12-06-writing-to-stderr-instead-of-stdout.html index c3dc1f9..26722a2 100644 --- a/docs/ch12-06-writing-to-stderr-instead-of-stdout.html +++ b/docs/ch12-06-writing-to-stderr-instead-of-stdout.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch13-00-functional-features.html b/docs/ch13-00-functional-features.html index 6796374..e241f67 100644 --- a/docs/ch13-00-functional-features.html +++ b/docs/ch13-00-functional-features.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch13-01-closures.html b/docs/ch13-01-closures.html index 258f109..3d9d013 100644 --- a/docs/ch13-01-closures.html +++ b/docs/ch13-01-closures.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch13-02-iterators.html b/docs/ch13-02-iterators.html index e6e18ae..42b8c3e 100644 --- a/docs/ch13-02-iterators.html +++ b/docs/ch13-02-iterators.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch13-03-improving-our-io-project.html b/docs/ch13-03-improving-our-io-project.html index 87ce6eb..15d806e 100644 --- a/docs/ch13-03-improving-our-io-project.html +++ b/docs/ch13-03-improving-our-io-project.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch13-04-performance.html b/docs/ch13-04-performance.html index 35d082e..93c30f6 100644 --- a/docs/ch13-04-performance.html +++ b/docs/ch13-04-performance.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch14-00-more-about-cargo.html b/docs/ch14-00-more-about-cargo.html index ce255d2..2738007 100644 --- a/docs/ch14-00-more-about-cargo.html +++ b/docs/ch14-00-more-about-cargo.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch14-01-release-profiles.html b/docs/ch14-01-release-profiles.html index 6849119..10b4943 100644 --- a/docs/ch14-01-release-profiles.html +++ b/docs/ch14-01-release-profiles.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch14-02-publishing-to-crates-io.html b/docs/ch14-02-publishing-to-crates-io.html index b3afaaa..c548572 100644 --- a/docs/ch14-02-publishing-to-crates-io.html +++ b/docs/ch14-02-publishing-to-crates-io.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch14-03-cargo-workspaces.html b/docs/ch14-03-cargo-workspaces.html index 9a3c96f..fad2cb4 100644 --- a/docs/ch14-03-cargo-workspaces.html +++ b/docs/ch14-03-cargo-workspaces.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch14-04-installing-binaries.html b/docs/ch14-04-installing-binaries.html index 4b84881..ab563a5 100644 --- a/docs/ch14-04-installing-binaries.html +++ b/docs/ch14-04-installing-binaries.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch14-05-extending-cargo.html b/docs/ch14-05-extending-cargo.html index 28cbd78..090f726 100644 --- a/docs/ch14-05-extending-cargo.html +++ b/docs/ch14-05-extending-cargo.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch15-00-smart-pointers.html b/docs/ch15-00-smart-pointers.html index 0487ae8..88a2133 100644 --- a/docs/ch15-00-smart-pointers.html +++ b/docs/ch15-00-smart-pointers.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch15-01-box.html b/docs/ch15-01-box.html index dd5fb6b..e9e2bd6 100644 --- a/docs/ch15-01-box.html +++ b/docs/ch15-01-box.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch15-02-deref.html b/docs/ch15-02-deref.html index 2291682..942b27b 100644 --- a/docs/ch15-02-deref.html +++ b/docs/ch15-02-deref.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch15-03-drop.html b/docs/ch15-03-drop.html index 0d5e648..54975e3 100644 --- a/docs/ch15-03-drop.html +++ b/docs/ch15-03-drop.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch15-04-rc.html b/docs/ch15-04-rc.html index 2b500c1..b1de0ee 100644 --- a/docs/ch15-04-rc.html +++ b/docs/ch15-04-rc.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch15-05-interior-mutability.html b/docs/ch15-05-interior-mutability.html index e2e0edf..cdb5c64 100644 --- a/docs/ch15-05-interior-mutability.html +++ b/docs/ch15-05-interior-mutability.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch15-06-reference-cycles.html b/docs/ch15-06-reference-cycles.html index 8ebe28e..97ffebf 100644 --- a/docs/ch15-06-reference-cycles.html +++ b/docs/ch15-06-reference-cycles.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch16-00-concurrency.html b/docs/ch16-00-concurrency.html index 10c247b..8d6c268 100644 --- a/docs/ch16-00-concurrency.html +++ b/docs/ch16-00-concurrency.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch16-01-threads.html b/docs/ch16-01-threads.html index ddb3896..9cf22aa 100644 --- a/docs/ch16-01-threads.html +++ b/docs/ch16-01-threads.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch16-02-message-passing.html b/docs/ch16-02-message-passing.html index 8be316a..a05e6b7 100644 --- a/docs/ch16-02-message-passing.html +++ b/docs/ch16-02-message-passing.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch16-03-shared-state.html b/docs/ch16-03-shared-state.html index 33c48f8..b720818 100644 --- a/docs/ch16-03-shared-state.html +++ b/docs/ch16-03-shared-state.html @@ -47,7 +47,7 @@
    diff --git a/docs/ch16-04-extensible-concurrency-sync-and-send.html b/docs/ch16-04-extensible-concurrency-sync-and-send.html index 18351f4..dd90e16 100644 --- a/docs/ch16-04-extensible-concurrency-sync-and-send.html +++ b/docs/ch16-04-extensible-concurrency-sync-and-send.html @@ -47,7 +47,7 @@
    @@ -101,6 +101,10 @@ commit 55b294f20fc846a13a9be623bf322d8b364cee77

    + +
    @@ -111,6 +115,10 @@ commit 55b294f20fc846a13a9be623bf322d8b364cee77

    + +
    diff --git a/docs/ch17-00-oop.html b/docs/ch17-00-oop.html new file mode 100644 index 0000000..b629dbf --- /dev/null +++ b/docs/ch17-00-oop.html @@ -0,0 +1,123 @@ + + + + + 面向对象 - Rust 程序设计语言 简体中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + +
    +

    Rust 是一个面向对象的编程语言吗?

    +
    +

    ch17-00-oop.md +
    +commit 759801361bde74b47e81755fff545c66020e6e63

    +
    +

    面向对象编程是一种起源于20世纪60年代Simula的模式化编程的方式,然后在90年代在C++语言开始流行。为了描述OOP有很多种复杂的定义:在一些定义下,Rust是面向对象的;在其他定义下,Rust不是。在本章节中,我们会探索一些被普遍认为是面向对象的特性和这些特性是如何转换为Rust的方言的。

    + +
    + + + + + + + + + + +
    + + + + + + + + + +
    + + + + + + + + + + + + diff --git a/docs/ch17-01-what-is-oo.html b/docs/ch17-01-what-is-oo.html new file mode 100644 index 0000000..10c3dd1 --- /dev/null +++ b/docs/ch17-01-what-is-oo.html @@ -0,0 +1,165 @@ + + + + + 什么是面向对象 - Rust 程序设计语言 简体中文版 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + +
    +

    什么是面向对象?

    +
    +

    ch17-01-what-is-oo.md +
    +commit 46334522e22d6217b392451cff8b4feca2d69d79

    +
    +

    关于一门语言是否需要是面向对象,在编程社区内并达成一致意见。Rust被很多不同的编程模式影响,我们探索了13章提到的函数式编程的特性。面向对象编程语言的一些特性往往是对象、封装和继承。我们看一下每个的含义和Rust是否支持它们。

    +

    对象包含数据和行为

    +

    Design Patterns: Elements of Reusable Object-Oriented Software这本书被俗称为The Gang of Four book,是面向对象编程模式的目录。它这样定义面向对象编程:

    +
    +

    面向对象的程序是由对象组成的。一个对象包数据和操作这些数据的程序。程序通常被称为方法或操作。

    +
    +

    在这个定一下,Rust是面向对象的:结构体和枚举包含数据和impl块提供了在结构体和枚举上的方法。虽然带有方法的结构体和枚举不称为对象,但是他们提供了和对象相同的功能,使用了Gang of Four定义的对象。

    +

    隐藏了实现细节的封装

    +

    通常与面向对象编程相关的另一个方面是封装的思想:对象的实现细节不能被使用对象的代码获取到。唯一与对象交互的方式是通过对象提供的public API,使用对象的代码无法深入到对象内部并直接改变数据或者行为。封装使得改变和重构对象的内部,无需改变使用对象的代码。

    +

    就像我们在第7张讨论的那样,我们可以使用pub关键字来决定模块、类型函数和方法是public的(默认情况下一切都是private)。比如,我们可以定义一个结构体AveragedCollection包含一个i32类型的vector。结构体也可以有一个字段,该字段保存了vector中所有值的平均值。这样,希望知道结构体中的vector的平均值的人可以随着获取到,而无需自己计算。AveragedCollection 会为我们缓存平均值结果。 Listing 17-1有AveragedCollection 结构体的定义。

    +

    Filename: src/lib.rs

    +
    pub struct AveragedCollection {
    +    list: Vec<i32>,
    +    average: f64,
    +}
    +
    +

    AveragedCollection结构体维护了一个Integer列表和集合中所有元素的平均值。

    +

    注意,结构体本身被标记为pub,这样其他代码可以使用这个结构体,但是在结构体内部的字段仍然是private。这是非常重要的,因为我们希望保证变量被增加到列表或者被从列表删除时,也会同时更新平均值。我们通过在结构体上实现add、remove和average方法来做到这一点( Listing 17-2:):

    +

    Filename: src/lib.rs

    +
    # pub struct AveragedCollection {
    +#     list: Vec<i32>,
    +#     average: f64,
    +# }
    +impl AveragedCollection {
    +    pub fn add(&mut self, value: i32) {
    +        self.list.push(value);
    +        self.update_average();
    +    }
    +
    +    pub fn remove(&mut self) -> Option<i32> {
    +        let result = self.list.pop();
    +        match result {
    +            Some(value) => {
    +                self.update_average();
    +                Some(value)
    +            },
    +            None => None,
    +        }
    +    }
    +
    +    pub fn average(&self) -> f64 {
    +        self.average
    +    }
    +
    +    fn update_average(&mut self) {
    +        let total: i32 = self.list.iter().sum();
    +        self.average = total as f64 / self.list.len() as f64;
    +    }
    +}
    +
    +

    Listing 17-2:在AveragedCollection结构体上实现了add、remove和average public方法

    + +
    + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + diff --git a/docs/index.html b/docs/index.html index c7abfb0..3ee5a0f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -46,7 +46,7 @@
    @@ -70,14 +70,14 @@

    ch01-00-introduction.md
    -commit 4f2dc564851dc04b271a2260c834643dfd86c724

    +commit 62f78bb3f7c222b574ff547d0161c2533691f9b4

    欢迎阅读“Rust 程序设计语言”,一本关于 Rust 的介绍性书籍。Rust 是一个着用于安全、速度和并发的编程语言。它的设计不仅可以使程序获得性能和对底层语言的控制,并且能够享受高级语言强大的抽象能力。这些特性使得 Rust 适合那些有类似 C 语言经验并正在寻找一个更安全的替代者的程序员,同时也适合那些来自类似 Python 语言背景,正在探索在不牺牲表现力的情况下编写更好性能代码的开发者。

    Rust 在编译时进行其绝大多数的安全检查和内存管理决策,因此程序的运行时性能没有受到影响。这让其在许多其他语言不擅长的应用场景中得以大显身手:存在可预测空间和时间要求的程序,嵌入到其他语言中,以及编写底层代码,如设备驱动和操作系统。Rust 也很擅长 web 程序:它驱动着 Rust 包注册网站(package registry site),crates.io!我们期待看到使用 Rust 进行创作。

    本书是为已经至少了解一门编程语言的读者而写的。读完本书之后,你应该能自如的编写 Rust 程序。我们将通过小而集中并且相互依赖的例子来学习 Rust,并向你展示如何使用 Rust 多样的功能,同时了解它们在幕后是如何执行的。

    为本书做出贡献

    -

    本书是开源的。如果你发现任何错误,请不要犹豫,在 GitHub 上发起 issue 或提交 pull request。

    +

    本书是开源的。如果你发现任何错误,请不要犹豫,在 GitHub 上发起 issue 或提交 pull request。请查看CONTRIBUTING.md获取更多信息。

    译者注:这是本译本的 GitHub 仓库,同样欢迎 Issue 和 PR :)

    diff --git a/docs/print.html b/docs/print.html index 51694c1..c0d834e 100644 --- a/docs/print.html +++ b/docs/print.html @@ -2,7 +2,7 @@ - 可扩展的并发:`Sync`和`Send` - Rust 程序设计语言 简体中文版 + 什么是面向对象 - Rust 程序设计语言 简体中文版 @@ -47,7 +47,7 @@
    @@ -71,14 +71,14 @@

    ch01-00-introduction.md
    -commit 4f2dc564851dc04b271a2260c834643dfd86c724

    +commit 62f78bb3f7c222b574ff547d0161c2533691f9b4

    欢迎阅读“Rust 程序设计语言”,一本关于 Rust 的介绍性书籍。Rust 是一个着用于安全、速度和并发的编程语言。它的设计不仅可以使程序获得性能和对底层语言的控制,并且能够享受高级语言强大的抽象能力。这些特性使得 Rust 适合那些有类似 C 语言经验并正在寻找一个更安全的替代者的程序员,同时也适合那些来自类似 Python 语言背景,正在探索在不牺牲表现力的情况下编写更好性能代码的开发者。

    Rust 在编译时进行其绝大多数的安全检查和内存管理决策,因此程序的运行时性能没有受到影响。这让其在许多其他语言不擅长的应用场景中得以大显身手:存在可预测空间和时间要求的程序,嵌入到其他语言中,以及编写底层代码,如设备驱动和操作系统。Rust 也很擅长 web 程序:它驱动着 Rust 包注册网站(package registry site),crates.io!我们期待看到使用 Rust 进行创作。

    本书是为已经至少了解一门编程语言的读者而写的。读完本书之后,你应该能自如的编写 Rust 程序。我们将通过小而集中并且相互依赖的例子来学习 Rust,并向你展示如何使用 Rust 多样的功能,同时了解它们在幕后是如何执行的。

    为本书做出贡献

    -

    本书是开源的。如果你发现任何错误,请不要犹豫,在 GitHub 上发起 issue 或提交 pull request。

    +

    本书是开源的。如果你发现任何错误,请不要犹豫,在 GitHub 上发起 issue 或提交 pull request。请查看CONTRIBUTING.md获取更多信息。

    译者注:这是本译本的 GitHub 仓库,同样欢迎 Issue 和 PR :)

    @@ -1836,7 +1836,7 @@ fn calculate_length(s: String) -> (String, usize) {

    ch04-02-references-and-borrowing.md
    -commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

    +commit 5e0546f53cce14b126527d9ba6d1b8eb212b4f3d

    在上一部分的结尾处的使用元组的代码是有问题的,我们需要将String返回给调用者函数这样就可以在调用calculate_length后仍然可以使用String了,因为String先被移动到了calculate_length

    下面是如何定义并使用一个(新的)calculate_length函数,它以一个对象的引用作为参数而不是获取值的所有权:

    @@ -2008,7 +2008,7 @@ for it to be borrowed from. // Danger!

因为s是在dangle创建的,当dangle的代码执行完毕后,s将被释放。不过我们尝试返回一个它的引用。这意味着这个引用会指向一个无效的String!这可不好。Rust 不会允许我们这么做的。

-

正确的代码是直接返回String

+

这里的解决方法是直接返回String

fn no_dangle() -> String {
     let s = String::from("hello");
 
@@ -2775,9 +2775,9 @@ not satisfied
 

ch06-02-match.md
-commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

+commit 64090418c23d615facfe49a8d548ad9baea6b097

-

Rust 有一个叫做match的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会讲到所有不同种类的模式以及他们的作用。match的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。

+

Rust 有一个叫做match的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及他们的作用。match的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。

match表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会检查match的每一个模式,并且在遇到第一个“符合”的模式时,值会进入相关联的代码块并在执行中被使用。

因为刚刚提到了硬币,让我们用他们来作为一个使用match的例子!我们可以编写一个函数来获取一个未知的(美国)硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如列表 6-3 中所示:

enum Coin {
@@ -3608,7 +3608,7 @@ commit e6d6caab41471f7115a621029bd428a812c5260e

ch08-01-vectors.md
-commit 4f2dc564851dc04b271a2260c834643dfd86c724

+commit 6c24544ba718bce0755bdaf03423af86280051d5

我们要讲到的第一个类型是Vec<T>,也被称为 vector。vector 允许我们在一个单独的数据结构中储存多于一个值,它在内存中彼此相邻的排列所有的值。vector 只能储存相同类型的值。他们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。

新建 vector

@@ -3696,7 +3696,9 @@ let row = vec![ SpreadsheetCell::Float(10.12), ];
-

Rust 在编译时就必须准确的知道 vector 中类型的原因是它需要知道储存每个元素到底需要多少内存。第二个优点是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加match意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。

+

Listing 8-1: Defining an enum to be able to hold +different types of data in a vector

+

Rust 在编译时就必须准确的知道 vector 中类型的原因是它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加match意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。

如果在编写程序时不能确切无遗的知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。相反,你可以使用 trait 对象,第十七章会讲到它。

现在我们了解了一些使用 vector 的最常见的方式,请一定去看看标准库中Vec定义的很多其他实用方法的 API 文档。例如,除了push之外还有一个pop方法,它会移除并返回 vector 的最后一个元素。让我们继续下一个集合类型:String

字符串

@@ -4010,7 +4012,7 @@ commit 4f2dc564851dc04b271a2260c834643dfd86c724

ch09-01-unrecoverable-errors-with-panic.md
-commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

+commit e26bb338ab14b98a850c3464e821d54940a45672

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

@@ -4063,28 +4065,40 @@ error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
$ 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
+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:1392
 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
+   1:     0x560ed90ec04c - std::sys::imp::backtrace::tracing::imp::write::hf33ae72d0baa11ed
+                        at /stable-dist-rustc/build/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:42
+   2:     0x560ed90ee03e - std::panicking::default_hook::{{closure}}::h59672b733cc6a455
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:351
+   3:     0x560ed90edc44 - std::panicking::default_hook::h1670459d2f3f8843
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:367
+   4:     0x560ed90ee41b - std::panicking::rust_panic_with_hook::hcf0ddb069e7abcd7
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:555
+   5:     0x560ed90ee2b4 - std::panicking::begin_panic::hd6eb68e27bdf6140
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:517
+   6:     0x560ed90ee1d9 - std::panicking::begin_panic_fmt::abcd5965948b877f8
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:501
+   7:     0x560ed90ee167 - rust_begin_unwind
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:477
+   8:     0x560ed911401d - core::panicking::panic_fmt::hc0f6d7b2c300cdd9
+                        at /stable-dist-rustc/build/src/libcore/panicking.rs:69
+   9:     0x560ed9113fc8 - core::panicking::panic_bounds_check::h02a4af86d01b3e96
+                        at /stable-dist-rustc/build/src/libcore/panicking.rs:56
+  10:     0x560ed90e71c5 - <collections::vec::Vec<T> as core::ops::Index<usize>>::index::h98abcd4e2a74c41
+                        at /stable-dist-rustc/build/src/libcollections/vec.rs:1392
+  11:     0x560ed90e727a - panic::main::h5d6b77c20526bc35
+                        at /home/you/projects/panic/src/main.rs:4
+  12:     0x560ed90f5d6a - __rust_maybe_catch_panic
+                        at /stable-dist-rustc/build/src/libpanic_unwind/lib.rs:98
+  13:     0x560ed90ee926 - std::rt::lang_start::hd7c880a37a646e81
+                        at /stable-dist-rustc/build/src/libstd/panicking.rs:436
+                        at /stable-dist-rustc/build/src/libstd/panic.rs:361
+                        at /stable-dist-rustc/build/src/libstd/rt.rs:57
+  14:     0x560ed90e7302 - main
+  15:     0x7f0d53f16400 - __libc_start_main
+  16:     0x560ed90e6659 - _start
+  17:                0x0 - <unknown>
 

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

@@ -4755,7 +4769,7 @@ fn main() {

ch10-02-traits.md
-commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

+commit e5a987f5da3fba24e55f5c7102ec63f9dc3bc360

trait 允许我们进行另一种抽象:他们让我们可以抽象类型所通用的行为。trait 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。在使用泛型类型参数的场景中,可以使用 trait bounds 在编译时指定泛型可以是任何实现了某个 trait 的类型,并由此在这个场景下拥有我们希望的功能。

@@ -4951,7 +4965,7 @@ error[E0507]: cannot move out of borrowed content | cannot move out of borrowed content

错误的核心是cannot move out of type [T], a non-copy array,对于非泛型版本的largest函数,我们只尝试了寻找最大的i32char。正如第四章讨论过的,像i32char这样的类型是已知大小的并可以储存在栈上,所以他们实现了Copy trait。当我们将largest函数改成使用泛型后,现在list参数的类型就有可能是没有实现Copy trait 的,这意味着我们可能不能将list[0]的值移动到largest变量中。

-

如果只想对实现了Copy的类型调用这些带啊吗,可以在T的 trait bounds 中增加Copy!列表 10-15 中展示了一个可以编译的泛型版本的largest函数的完整代码,只要传递给largest的 slice 值的类型实现了PartialOrdCopy这两个 trait,例如i32char

+

如果只想对实现了Copy的类型调用这些代码,可以在T的 trait bounds 中增加Copy!列表 10-15 中展示了一个可以编译的泛型版本的largest函数的完整代码,只要传递给largest的 slice 值的类型实现了PartialOrdCopy这两个 trait,例如i32char

Filename: src/main.rs

use std::cmp::PartialOrd;
 
@@ -6232,22 +6246,34 @@ fn it_adds_two() {
 

总结

Rust 的测试功能提供了一个确保即使做出改变函数也能继续以指定方式运行的途径。单元测试独立的验证库的不同部分并能够测试私有实现细节。集成测试则涉及多个部分结合起来工作时的用例,并像其他代码那样测试库的公有 API。即使 Rust 的类型系统和所有权规则可以帮助避免一些 bug,不过测试对于减少代码是否符合期望相关的逻辑 bug 是很重要的。

接下来让我们结合本章所学和其他之前章节的知识,在下一章一起编写一个项目!

-

一个 I/O 项目

+

一个 I/O 项目:构建一个小巧的 grep

ch12-00-an-io-project.md
-commit 4f2dc564851dc04b271a2260c834643dfd86c724

+commit 1f432fc231cfbc310433ab2a354d77058444288c

-

之前几个章节我们学习了很多知识。让我们一起运用这些新知识来构建一个项目。在这个过程中,我们还将学习到更多 Rust 标准库的内容。

-

那么我们应该写点什么呢?这得是一个利用 Rust 优势的项目。Rust 的一个强大的用途是命令行工具:Rust 的运行速度、安全性、“单二进制文件”输出和跨平台支持使得它称为这类工作的绝佳选择。所以我们将创建一个我们自己的经典命令行工具:grepgrep有着极为简单的应用场景,它完成如下工作:

-
    -
  1. 它获取一个文件和一个字符串作为参数。
  2. + + +

    本章既是一个目前所学的很多技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。

    +

    Rust 的运行速度、安全性、“单二进制文件”输出和跨平台支持使其成为创建命令行程序的绝佳选择,所以我们的项目将创建一个我们自己版本的经典命令行工具:grep。grep 是“Globally search a Regular Expression and Print.”的首字母缩写。grep最简单的使用场景是使用如下步骤在特定文件中搜索指定字符串:

    +
      +
    • 获取一个文件和一个字符串作为参数。
    • 读取文件
    • 寻找文件中包含字符串参数的行
    • 打印出这些行
    • -
-

另外,我们还将添加一个额外的功能:一个环境变量允许我们大小写不敏感的搜索字符串参数。

-

还有另一个很好的理由使用grep作为示例项目:Rust 社区的成员,Andrew Gallant,已经使用 Rust 创建了一个功能非常完整的grep版本。它叫做ripgrep,并且它非常非常快。这样虽然我们的grep将会非常简单,你也会掌握阅读现真实项目的基础知识。

+ +

我们还会展示如何使用环境变量和打印到标准错误而不是标准输出;这些功能在命令行工具中是很常用的。

+

一位 Rust 社区的成员,Andrew Gallant,已经创建了一个功能完整且非常快速的grep版本,叫做ripgrep。相比之下,我们的grep将非常简单,本章将交给你一些帮助你理解像ripgrep这样真实项目的背景知识。

这个项目将会结合之前所学的一些内容:

  • 代码组织(使用第七章学习的模块)
  • @@ -6256,26 +6282,41 @@ commit 4f2dc564851dc04b271a2260c834643dfd86c724

  • 合理的使用 trait 和生命周期(第十章)
  • 测试(第十一章)
-

另外,我还会简要的讲到闭包、迭代器和 trait 对象,他们分别会在第XX、YY和ZZ章详细介绍。

-

让我们一如既往的使用cargo new创建一个新项目:

-
$ cargo new --bin greprs
+

另外,我还会简要的讲到闭包、迭代器和 trait 对象,他们分别会在第十三章和第十七章中详细介绍。

+

让我们一如既往的使用cargo new创建一个新项目。我们称之为greprs以便与可能已经安装在系统上的grep工具相区别:

+
$ cargo new --bin greprs
      Created binary (application) `greprs` project
 $ cd greprs
 
-

我们版本的grep的叫做“greprs”,这样就不会迷惑用户让他们以为这就是可能已经在系统上安装了功能更完整的grep

接受命令行参数

ch12-01-accepting-command-line-arguments.md
-commit c49e5ee8859f8eb8f8867cbeafbdf5b802aa5894

+commit b8e4fcbf289b82c12121b282747ce05180afb1fb

-

第一个任务是让greprs接受两个命令行参数。crates.io 上有一些现存的库可以帮助我们,不过因为我们正在学习,我们将自己实现一个。

-

我们需要调用一个 Rust 标准库提供的函数:std::env::args。这个函数返回一个传递给程序的命令行参数的迭代器iterator)。我们还未讨论到迭代器,第十三章会全面的介绍他们。但是对于我们的目的来说,使用他们并不需要知道多少技术细节。我们只需要明白两点:

+

第一个任务是让greprs能够接受两个命令行参数:文件名和要搜索的字符串。也就是说希望能够使用cargo run,要搜索的字符串和被搜索的文件的路径来运行程序,像这样:

+
$ cargo run searchstring example-filename.txt
+
+

现在cargo new生成的程序忽略任何传递给它的参数。crates.io 上有一些现存的可以帮助我们接受命令行参数的库,不过因为我们正在学习,让我们实现一个。

+ + +

读取参数值

+

为了能够获取传递给程序的命令行参数的值,我们需要调用一个 Rust 标准库提供的函数:std::env::args。这个函数返回一个传递给程序的命令行参数的迭代器iterator)。我们还未讨论到迭代器,第十三章会全面的介绍他们。但是对于我们现在的目的来说只需要明白两点:

  1. 迭代器生成一系列的值。
  2. 在迭代器上调用collect方法可以将其生成的元素转换为一个 vector。
-

让我们试试列表 12-1 中的代码:

+

让我们尝试一下:使用列表 12-1 中的代码来读取任何传递给greprs的命令行参数并将其收集到一个 vector 中。

+ +

Filename: src/main.rs

use std::env;
 
@@ -6284,11 +6325,20 @@ fn main() {
     println!("{:?}", args);
 }
 
-

Listing 12-1: Collect the command line arguments into a -vector and print them out

+

Listing 12-1: Collect the command line arguments into a vector and print them +out

-

首先使用use语句来将std::env模块引入作用域。当函数嵌套了多于一层模块时,比如说std::env::args,通常使用use将父模块引入作用域,而不是引入其本身。env::args比单独的args要明确一些。当然,如果使用了多于一个std::env中的函数时,我们也只需要一个use语句。

-

main函数的第一行,我们调用了env::args,并立即使用collect来创建了一个 vector。这里我们也显式的注明了args的类型:collect可以被用来创建很多类型的集合。Rust 并不能推断出我们需要什么类型,所以类型注解是必须的。在 Rust 中我们很少会需要注明类型,不过collect是就一个通常需要这么做的函数。

+

首先使用use语句来将std::env模块引入作用域以便可以使用它的args函数。注意std::env::args函数嵌套进了两层模块中。如第七章讲到的,当所需函数嵌套了多于一层模块时,通常将父模块引入作用域,而不是其自身。这便于我们利用std::env中的其他函数。这比增加了use std::env::args;后仅仅使用args调用函数要更明确一些;这样看起来好像一个定义于当前模块的函数。

+ + +
+

注意:std::env::args在其任何参数包含无效 Unicode 字符时会 panic。如果你需要接受包含无效 Unicode 字符的参数,使用std::env::args_os代替。这个函数返回OsString值而不是String值。出于简单考虑这里使用std::env::args,因为OsString值每个平台都不一样而且比String值处理起来更复杂。

+
+ + + +

main函数的第一行,我们调用了env::args,并立即使用collect来创建了一个包含迭代器所有值的 vector。collect可以被用来创建很多类型的集合,所以这里显式注明的args类型来指定我们需要一个字符串 vector。虽然在 Rust 中我们很少会需要注明类型,collect就是一个经常需要注明类型的函数,因为 Rust 不能推断出你想要什么类型的集合。

最后,我们使用调试格式:?打印出 vector。让我们尝试不用参数运行代码,接着用两个参数:

$ cargo run
 ["target/debug/greprs"]
@@ -6297,49 +6347,56 @@ $ cargo run needle haystack
 ...snip...
 ["target/debug/greprs", "needle", "haystack"]
 
-

你会注意一个有趣的事情:二进制文件的名字是第一个参数。其原因超出了本章介绍的范围,不过这是我们必须记住的。

-

现在我们有了一个访问所有参数的方法,让我们如列表 12-2 中所示将需要的变量存放到变量中:

+ + +

你可能注意到了 vector 的第一个值是"target/debug/greprs",它是二进制我呢见的名称。其原因超出了本章介绍的范围,不过需要记住的是我们保存了所需的两个参数。

+

将参数值保存进变量

+

打印出参数 vector 中的值仅仅展示了可以访问程序中指定为命令行参数的值。但是这并不是我们想要做的,我们希望将这两个参数的值保存进变量这样就可以在程序使用这些值。让我们如列表 12-2 这样做:

+ +

Filename: src/main.rs

-
use std::env;
+
use std::env;
 
 fn main() {
     let args: Vec<String> = env::args().collect();
 
-    let search = &args[1];
+    let query = &args[1];
     let filename = &args[2];
 
-    println!("Searching for {}", search);
+    println!("Searching for {}", query);
     println!("In file {}", filename);
 }
 
-

Listing 12-2: Create variables to hold the search -argument and filename argument

+

Listing 12-2: Create variables to hold the query argument and filename argument

-

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

+

正如我们在打印出 vector 时所看到的,程序的名称占据了 vector 的第一个值args[0],所以我们从索引1开始。第一个参数greprs是需要搜索的字符串,所以将其将第一个参数的引用存放在变量query中。第二个参数将是文件名,所以将第二个参数的引用放入变量filename中。

+

我们将临时打印出出这些变量的值,再一次证明代码如我们期望的那样工作。让我们使用参数testsample.txt再次运行这个程序:

$ cargo run test sample.txt
     Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
-     Running `target\debug\greprs.exe test sample.txt`
+     Running `target/debug/greprs 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 c49e5ee8859f8eb8f8867cbeafbdf5b802aa5894

+commit b8e4fcbf289b82c12121b282747ce05180afb1fb

-

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

+

接下来我们将读取由命令行文件名参数指定的文件。首先,需要一个用来测试的示例文件——用来确保greprs正常工作的最好的文件是拥有少量文本和多个行且有一些重复单词的文件。列表 12-3 是一首艾米莉·狄金森(Emily Dickinson)的诗,它正适合这个工作!在项目根目录创建一个文件poem.txt,并输入诗 "I'm nobody! Who are you?":

Filename: poem.txt

-
I'm nobody! Who are you?
+
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.
@@ -6349,23 +6406,27 @@ How public, like a frog
 To tell your name the livelong day
 To an admiring bog!
 
+

Listing 12-3: The poem "I'm nobody! Who are you?" by +Emily Dickinson that will make a good test case

-

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

+ + +

创建完这个文件之后,修改 src/main.rs 并增加如列表 12-4 所示的打开文件的代码:

Filename: src/main.rs

-
use std::env;
+
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 query = &args[1];
     let filename = &args[2];
 
-    println!("Searching for {}", search);
+    println!("Searching for {}", query);
     println!("In file {}", filename);
 
     let mut f = File::open(filename).expect("file not found");
@@ -6376,15 +6437,15 @@ fn main() {
     println!("With text:\n{}", contents);
 }
 
-

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

+

Listing 12-4: Reading 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 文件将作为第二个参数:

+

首先,增加了更多的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中,我们增加了三点内容:第一,通过传递变量filename的值调用File::open函数的值来获取文件的可变句柄。创建了叫做contents的变量并将其设置为一个可变的,空的String。它将会存放之后读取的文件的内容。第三,对文件句柄调用read_to_string并传递contents的可变引用作为参数。

+

在这些代码之后,我们再次增加了临时的println!打印出读取文件后contents的值,这样就可以检查目前为止的程序能否工作。

+

尝试运行这些代码,随意指定一个字符串作为第一个命令行参数(因为还未实现搜索功能的部分)而将 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`
+     Running `target/debug/greprs the poem.txt`
 Searching for the
 In file poem.txt
 With text:
@@ -6398,18 +6459,19 @@ How public, like a frog
 To tell your name the livelong day
 To an admiring bog!
 
-

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

+

好的!代码读取并打印出了文件的内容。虽然它还有一些瑕疵:main函数有着多个功能,同时也没有处理可能出现的错误。虽然我们的程序还很小,这些瑕疵并不是什么大问题。不过随着程序功能的丰富,将会越来越难以用简单的方法修复他们。在开发程序时,及早开始重构是一个最佳实践,因为重构少量代码时要容易的多,所以让我们现在就开始吧。

读取文件

ch12-03-improving-error-handling-and-modularity.md
-commit bdab3f38da5b7bf7277bfe21ec59a7a81880e6b4

+commit b8e4fcbf289b82c12121b282747ce05180afb1fb

-

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

-

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

-

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

-

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

-

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

+

为了改善我们的程序这里有四个问题需要修复,而且他们都与程序的组织方式和如何处理潜在错误有关。

+

第一,main现在进行了两个任务:它解析了参数并打开了文件。对于一个这样的小函数,这并不是一个大问题。然而如果main中的功能持续增加,main函数处理的单独的任务也会增加。当函数承担了更多责任,它就更难以推导,更难以测试,并且更难以在不破坏其他部分的情况下做出修改。最好能分离出功能这样每个函数就负责一个任务。

+

这同时也关系到第二个问题:searchfilename是程序中的配置变量,而像fcontents则用来执行程序逻辑。随着main函数的增长,就需要引入更多的变量到作用域中,而当作用域中有更多的变量时,将更难以追踪每个变量的目的。最好能将将配置变量组织进一个结构这样就能使他们的目的更明确了。

+

第三个问题是如果打开文件失败我们使用expect来打印出错误信息,不过这个错误信息只是说file not found。除了缺少文件之外还有很多打开文件可能失败的方式:例如,文件可能存在,不过可能没有打开它的权限。如果我们现在就出于这种情况,打印出的file not found错误信息就给了用户一个不符合事实的建议!

+

第四,我们不停的使用expect来处理不同的错误,如果用户没有指定足够的参数来运行程序,他们会从 Rust 得到 "index out of bounds" 错误而这并不能明确的解释问题。如果所有的错误处理都位于一处这样将来的维护者在需要修改错误处理逻辑时就只需要咨询一处代码。将所有的错误处理都放在一处也有助于确保我们打印的错误信息对终端用户来说是有意义的。

+

让我们通过重构项目来解决这些问题。

二进制项目的关注分离

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

    @@ -9446,6 +9508,70 @@ commit 55b294f20fc846a13a9be623bf322d8b364cee77

    正如我们提到的,因为 Rust 本身很少有处理并发的部分内容,有很多的并发方案都由 crate 实现。他们比标准库要发展的更快;请在网上搜索当前最新的用于多线程场景的 crate。

    Rust 提供了用于消息传递的通道,和像Mutex<T>Arc<T>这样可以安全的用于并发上下文的智能指针。类型系统和借用检查器会确保这些场景中的代码不会出现数据竞争和无效的引用。一旦代码可以编译了,我们就可以坚信这些代码可以正确的运行于多线程环境,而不会出现其他语言中经常出现的那些难以追踪的 bug。并发编程不再是什么可怕的概念:无所畏惧的使你的程序使用并发吧!

    接下来,让我们讨论一下当 Rust 程序变得更大时那些符合习惯的模拟问题和结构的解决方案,以及 Rust 风格如何与面向对象编程(Object Oriented Programming)中那些你所熟悉的概念相联系。

    +

    Rust 是一个面向对象的编程语言吗?

    +
    +

    ch17-00-oop.md +
    +commit 759801361bde74b47e81755fff545c66020e6e63

    +
    +

    面向对象编程是一种起源于20世纪60年代Simula的模式化编程的方式,然后在90年代在C++语言开始流行。为了描述OOP有很多种复杂的定义:在一些定义下,Rust是面向对象的;在其他定义下,Rust不是。在本章节中,我们会探索一些被普遍认为是面向对象的特性和这些特性是如何转换为Rust的方言的。

    +

    什么是面向对象?

    +
    +

    ch17-01-what-is-oo.md +
    +commit 46334522e22d6217b392451cff8b4feca2d69d79

    +
    +

    关于一门语言是否需要是面向对象,在编程社区内并达成一致意见。Rust被很多不同的编程模式影响,我们探索了13章提到的函数式编程的特性。面向对象编程语言的一些特性往往是对象、封装和继承。我们看一下每个的含义和Rust是否支持它们。

    +

    对象包含数据和行为

    +

    Design Patterns: Elements of Reusable Object-Oriented Software这本书被俗称为The Gang of Four book,是面向对象编程模式的目录。它这样定义面向对象编程:

    +
    +

    面向对象的程序是由对象组成的。一个对象包数据和操作这些数据的程序。程序通常被称为方法或操作。

    +
    +

    在这个定一下,Rust是面向对象的:结构体和枚举包含数据和impl块提供了在结构体和枚举上的方法。虽然带有方法的结构体和枚举不称为对象,但是他们提供了和对象相同的功能,使用了Gang of Four定义的对象。

    +

    隐藏了实现细节的封装

    +

    通常与面向对象编程相关的另一个方面是封装的思想:对象的实现细节不能被使用对象的代码获取到。唯一与对象交互的方式是通过对象提供的public API,使用对象的代码无法深入到对象内部并直接改变数据或者行为。封装使得改变和重构对象的内部,无需改变使用对象的代码。

    +

    就像我们在第7张讨论的那样,我们可以使用pub关键字来决定模块、类型函数和方法是public的(默认情况下一切都是private)。比如,我们可以定义一个结构体AveragedCollection包含一个i32类型的vector。结构体也可以有一个字段,该字段保存了vector中所有值的平均值。这样,希望知道结构体中的vector的平均值的人可以随着获取到,而无需自己计算。AveragedCollection 会为我们缓存平均值结果。 Listing 17-1有AveragedCollection 结构体的定义。

    +

    Filename: src/lib.rs

    +
    pub struct AveragedCollection {
    +    list: Vec<i32>,
    +    average: f64,
    +}
    +
    +

    AveragedCollection结构体维护了一个Integer列表和集合中所有元素的平均值。

    +

    注意,结构体本身被标记为pub,这样其他代码可以使用这个结构体,但是在结构体内部的字段仍然是private。这是非常重要的,因为我们希望保证变量被增加到列表或者被从列表删除时,也会同时更新平均值。我们通过在结构体上实现add、remove和average方法来做到这一点( Listing 17-2:):

    +

    Filename: src/lib.rs

    +
    # pub struct AveragedCollection {
    +#     list: Vec<i32>,
    +#     average: f64,
    +# }
    +impl AveragedCollection {
    +    pub fn add(&mut self, value: i32) {
    +        self.list.push(value);
    +        self.update_average();
    +    }
    +
    +    pub fn remove(&mut self) -> Option<i32> {
    +        let result = self.list.pop();
    +        match result {
    +            Some(value) => {
    +                self.update_average();
    +                Some(value)
    +            },
    +            None => None,
    +        }
    +    }
    +
    +    pub fn average(&self) -> f64 {
    +        self.average
    +    }
    +
    +    fn update_average(&mut self) {
    +        let total: i32 = self.list.iter().sum();
    +        self.average = total as f64 / self.list.len() as f64;
    +    }
    +}
    +
    +

    Listing 17-2:在AveragedCollection结构体上实现了add、remove和average public方法

diff --git a/src/PREFACE.md b/src/PREFACE.md index c8965ee..16d2525 100644 --- a/src/PREFACE.md +++ b/src/PREFACE.md @@ -1,7 +1,7 @@ # Rust 程序设计语言(第二版) 简体中文版 -还在施工中:目前翻译到第十六章 +还在施工中:目前翻译到第十六章,正在更新第十二章 -目前官方进度:[第十六章](https://github.com/rust-lang/book/projects/1)(17~20 章还在编写当中) +目前官方进度:[第十七章](https://github.com/rust-lang/book/projects/1)(18~20 章还在编写当中) GitBook 代码排版已大体解决,已不影响阅读 \ No newline at end of file diff --git a/src/ch01-00-introduction.md b/src/ch01-00-introduction.md index 31a1d15..ce17d46 100644 --- a/src/ch01-00-introduction.md +++ b/src/ch01-00-introduction.md @@ -2,7 +2,7 @@ > [ch01-00-introduction.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch01-00-introduction.md) >
-> commit 4f2dc564851dc04b271a2260c834643dfd86c724 +> commit 62f78bb3f7c222b574ff547d0161c2533691f9b4 欢迎阅读“Rust 程序设计语言”,一本关于 Rust 的介绍性书籍。Rust 是一个着用于安全、速度和并发的编程语言。它的设计不仅可以使程序获得性能和对底层语言的控制,并且能够享受高级语言强大的抽象能力。这些特性使得 Rust 适合那些有类似 C 语言经验并正在寻找一个更安全的替代者的程序员,同时也适合那些来自类似 Python 语言背景,正在探索在不牺牲表现力的情况下编写更好性能代码的开发者。 @@ -15,9 +15,10 @@ registry site),[crates.io]!我们期待看到**你**使用 Rust 进行创 ## 为本书做出贡献 -本书是开源的。如果你发现任何错误,请不要犹豫,[在 GitHub 上][on GitHub]发起 issue 或提交 pull request。 +本书是开源的。如果你发现任何错误,请不要犹豫,[在 GitHub 上][on GitHub]发起 issue 或提交 pull request。请查看[CONTRIBUTING.md]获取更多信息。 [on GitHub]: https://github.com/rust-lang/book +[CONTRIBUTING.md]: https://github.com/rust-lang/book/blob/master/CONTRIBUTING.md > 译者注:这是本译本的 [GitHub 仓库][trpl-zh-cn],同样欢迎 Issue 和 PR :) diff --git a/src/ch04-02-references-and-borrowing.md b/src/ch04-02-references-and-borrowing.md index d07764d..a8bf6ad 100644 --- a/src/ch04-02-references-and-borrowing.md +++ b/src/ch04-02-references-and-borrowing.md @@ -2,7 +2,7 @@ > [ch04-02-references-and-borrowing.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch04-02-references-and-borrowing.md) >
-> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56 +> commit 5e0546f53cce14b126527d9ba6d1b8eb212b4f3d 在上一部分的结尾处的使用元组的代码是有问题的,我们需要将`String`返回给调用者函数这样就可以在调用`calculate_length`后仍然可以使用`String`了,因为`String`先被移动到了`calculate_length`。 @@ -243,7 +243,7 @@ fn dangle() -> &String { // dangle returns a reference to a String 因为`s`是在`dangle`创建的,当`dangle`的代码执行完毕后,`s`将被释放。不过我们尝试返回一个它的引用。这意味着这个引用会指向一个无效的`String`!这可不好。Rust 不会允许我们这么做的。 -正确的代码是直接返回`String`: +这里的解决方法是直接返回`String`: ```rust fn no_dangle() -> String { diff --git a/src/ch06-02-match.md b/src/ch06-02-match.md index e9ea8d8..7e482c0 100644 --- a/src/ch06-02-match.md +++ b/src/ch06-02-match.md @@ -2,9 +2,9 @@ > [ch06-02-match.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-02-match.md) >
-> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56 +> commit 64090418c23d615facfe49a8d548ad9baea6b097 -Rust 有一个叫做`match`的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会讲到所有不同种类的模式以及他们的作用。`match`的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。 +Rust 有一个叫做`match`的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及他们的作用。`match`的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。 把`match`表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会检查`match`的每一个模式,并且在遇到第一个“符合”的模式时,值会进入相关联的代码块并在执行中被使用。 diff --git a/src/ch08-01-vectors.md b/src/ch08-01-vectors.md index 3a86c92..7c6b1f8 100644 --- a/src/ch08-01-vectors.md +++ b/src/ch08-01-vectors.md @@ -2,7 +2,7 @@ > [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-01-vectors.md) >
-> commit 4f2dc564851dc04b271a2260c834643dfd86c724 +> commit 6c24544ba718bce0755bdaf03423af86280051d5 我们要讲到的第一个类型是`Vec`,也被称为 *vector*。vector 允许我们在一个单独的数据结构中储存多于一个值,它在内存中彼此相邻的排列所有的值。vector 只能储存相同类型的值。他们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。 @@ -133,7 +133,10 @@ let row = vec![ ]; ``` -Rust 在编译时就必须准确的知道 vector 中类型的原因是它需要知道储存每个元素到底需要多少内存。第二个优点是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加`match`意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。 +Listing 8-1: Defining an enum to be able to hold +different types of data in a vector + +Rust 在编译时就必须准确的知道 vector 中类型的原因是它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加`match`意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。 如果在编写程序时不能确切无遗的知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。相反,你可以使用 trait 对象,第十七章会讲到它。 diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index 264daa1..5d0e6c9 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -2,7 +2,7 @@ > [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-01-unrecoverable-errors-with-panic.md) >
-> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56 +> commit e26bb338ab14b98a850c3464e821d54940a45672 突然有一天,糟糕的事情发生了,而你对此束手无策。对于这种情况,Rust 有`panic!`宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。 @@ -80,28 +80,40 @@ error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) $ 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 +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:1392 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 + 1: 0x560ed90ec04c - std::sys::imp::backtrace::tracing::imp::write::hf33ae72d0baa11ed + at /stable-dist-rustc/build/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:42 + 2: 0x560ed90ee03e - std::panicking::default_hook::{{closure}}::h59672b733cc6a455 + at /stable-dist-rustc/build/src/libstd/panicking.rs:351 + 3: 0x560ed90edc44 - std::panicking::default_hook::h1670459d2f3f8843 + at /stable-dist-rustc/build/src/libstd/panicking.rs:367 + 4: 0x560ed90ee41b - std::panicking::rust_panic_with_hook::hcf0ddb069e7abcd7 + at /stable-dist-rustc/build/src/libstd/panicking.rs:555 + 5: 0x560ed90ee2b4 - std::panicking::begin_panic::hd6eb68e27bdf6140 + at /stable-dist-rustc/build/src/libstd/panicking.rs:517 + 6: 0x560ed90ee1d9 - std::panicking::begin_panic_fmt::abcd5965948b877f8 + at /stable-dist-rustc/build/src/libstd/panicking.rs:501 + 7: 0x560ed90ee167 - rust_begin_unwind + at /stable-dist-rustc/build/src/libstd/panicking.rs:477 + 8: 0x560ed911401d - core::panicking::panic_fmt::hc0f6d7b2c300cdd9 + at /stable-dist-rustc/build/src/libcore/panicking.rs:69 + 9: 0x560ed9113fc8 - core::panicking::panic_bounds_check::h02a4af86d01b3e96 + at /stable-dist-rustc/build/src/libcore/panicking.rs:56 + 10: 0x560ed90e71c5 - as core::ops::Index>::index::h98abcd4e2a74c41 + at /stable-dist-rustc/build/src/libcollections/vec.rs:1392 + 11: 0x560ed90e727a - panic::main::h5d6b77c20526bc35 + at /home/you/projects/panic/src/main.rs:4 + 12: 0x560ed90f5d6a - __rust_maybe_catch_panic + at /stable-dist-rustc/build/src/libpanic_unwind/lib.rs:98 + 13: 0x560ed90ee926 - std::rt::lang_start::hd7c880a37a646e81 + at /stable-dist-rustc/build/src/libstd/panicking.rs:436 + at /stable-dist-rustc/build/src/libstd/panic.rs:361 + at /stable-dist-rustc/build/src/libstd/rt.rs:57 + 14: 0x560ed90e7302 - main + 15: 0x7f0d53f16400 - __libc_start_main + 16: 0x560ed90e6659 - _start + 17: 0x0 - ``` Listing 9-1: The backtrace generated by a call to diff --git a/src/ch10-02-traits.md b/src/ch10-02-traits.md index d535908..9a5f68e 100644 --- a/src/ch10-02-traits.md +++ b/src/ch10-02-traits.md @@ -2,7 +2,7 @@ > [ch10-02-traits.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch10-02-traits.md) >
-> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56 +> commit e5a987f5da3fba24e55f5c7102ec63f9dc3bc360 trait 允许我们进行另一种抽象:他们让我们可以抽象类型所通用的行为。*trait* 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。在使用泛型类型参数的场景中,可以使用 *trait bounds* 在编译时指定泛型可以是任何实现了某个 trait 的类型,并由此在这个场景下拥有我们希望的功能。 diff --git a/src/ch12-00-an-io-project.md b/src/ch12-00-an-io-project.md index 26be4d8..cc6d146 100644 --- a/src/ch12-00-an-io-project.md +++ b/src/ch12-00-an-io-project.md @@ -1,21 +1,35 @@ -# 一个 I/O 项目 +# 一个 I/O 项目:构建一个小巧的 grep > [ch12-00-an-io-project.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-00-an-io-project.md) >
-> commit 4f2dc564851dc04b271a2260c834643dfd86c724 +> commit 1f432fc231cfbc310433ab2a354d77058444288c -之前几个章节我们学习了很多知识。让我们一起运用这些新知识来构建一个项目。在这个过程中,我们还将学习到更多 Rust 标准库的内容。 + -那么我们应该写点什么呢?这得是一个利用 Rust 优势的项目。Rust 的一个强大的用途是命令行工具:Rust 的运行速度、安全性、“单二进制文件”输出和跨平台支持使得它称为这类工作的绝佳选择。所以我们将创建一个我们自己的经典命令行工具:`grep`。`grep`有着极为简单的应用场景,它完成如下工作: + -1. 它获取一个文件和一个字符串作为参数。 -2. 读取文件 -3. 寻找文件中包含字符串参数的行 -4. 打印出这些行 +本章既是一个目前所学的很多技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。 -另外,我们还将添加一个额外的功能:一个环境变量允许我们大小写不敏感的搜索字符串参数。 +Rust 的运行速度、安全性、“单二进制文件”输出和跨平台支持使其成为创建命令行程序的绝佳选择,所以我们的项目将创建一个我们自己版本的经典命令行工具:`grep`。grep 是“Globally search a Regular Expression and Print.”的首字母缩写。`grep`最简单的使用场景是使用如下步骤在特定文件中搜索指定字符串: -还有另一个很好的理由使用`grep`作为示例项目:Rust 社区的成员,Andrew Gallant,已经使用 Rust 创建了一个功能非常完整的`grep`版本。它叫做`ripgrep`,并且它非常非常快。这样虽然我们的`grep`将会非常简单,你也会掌握阅读现真实项目的基础知识。 +- 获取一个文件和一个字符串作为参数。 +- 读取文件 +- 寻找文件中包含字符串参数的行 +- 打印出这些行 + +我们还会展示如何使用环境变量和打印到标准错误而不是标准输出;这些功能在命令行工具中是很常用的。 + +一位 Rust 社区的成员,Andrew Gallant,已经创建了一个功能完整且非常快速的`grep`版本,叫做`ripgrep`。相比之下,我们的`grep`将非常简单,本章将交给你一些帮助你理解像`ripgrep`这样真实项目的背景知识。 这个项目将会结合之前所学的一些内容: @@ -25,14 +39,12 @@ - 合理的使用 trait 和生命周期(第十章) - 测试(第十一章) -另外,我还会简要的讲到闭包、迭代器和 trait 对象,他们分别会在第XX、YY和ZZ章详细介绍。 +另外,我还会简要的讲到闭包、迭代器和 trait 对象,他们分别会在第十三章和第十七章中详细介绍。 -让我们一如既往的使用`cargo new`创建一个新项目: +让我们一如既往的使用`cargo new`创建一个新项目。我们称之为`greprs`以便与可能已经安装在系统上的`grep`工具相区别: -```text +``` $ cargo new --bin greprs Created binary (application) `greprs` project $ cd greprs -``` - -我们版本的`grep`的叫做“greprs”,这样就不会迷惑用户让他们以为这就是可能已经在系统上安装了功能更完整的`grep`。 \ No newline at end of file +``` \ 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 006dd3a..ef608ac 100644 --- a/src/ch12-01-accepting-command-line-arguments.md +++ b/src/ch12-01-accepting-command-line-arguments.md @@ -2,16 +2,38 @@ > [ch12-01-accepting-command-line-arguments.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-01-accepting-command-line-arguments.md) >
-> commit c49e5ee8859f8eb8f8867cbeafbdf5b802aa5894 +> commit b8e4fcbf289b82c12121b282747ce05180afb1fb -第一个任务是让`greprs`接受两个命令行参数。crates.io 上有一些现存的库可以帮助我们,不过因为我们正在学习,我们将自己实现一个。 +第一个任务是让`greprs`能够接受两个命令行参数:文件名和要搜索的字符串。也就是说希望能够使用`cargo run`,要搜索的字符串和被搜索的文件的路径来运行程序,像这样: -我们需要调用一个 Rust 标准库提供的函数:`std::env::args`。这个函数返回一个传递给程序的命令行参数的**迭代器**(*iterator*)。我们还未讨论到迭代器,第十三章会全面的介绍他们。但是对于我们的目的来说,使用他们并不需要知道多少技术细节。我们只需要明白两点: +``` +$ cargo run searchstring example-filename.txt +``` + +现在`cargo new`生成的程序忽略任何传递给它的参数。crates.io 上有一些现存的可以帮助我们接受命令行参数的库,不过因为我们正在学习,让我们实现一个。 + + + + +### 读取参数值 + +为了能够获取传递给程序的命令行参数的值,我们需要调用一个 Rust 标准库提供的函数:`std::env::args`。这个函数返回一个传递给程序的命令行参数的**迭代器**(*iterator*)。我们还未讨论到迭代器,第十三章会全面的介绍他们。但是对于我们现在的目的来说只需要明白两点: 1. 迭代器生成一系列的值。 2. 在迭代器上调用`collect`方法可以将其生成的元素转换为一个 vector。 -让我们试试列表 12-1 中的代码: +让我们尝试一下:使用列表 12-1 中的代码来读取任何传递给`greprs`的命令行参数并将其收集到一个 vector 中。 + + + Filename: src/main.rs @@ -24,14 +46,26 @@ fn main() { } ``` -Listing 12-1: Collect the command line arguments into a -vector and print them out +Listing 12-1: Collect the command line arguments into a vector and print them +out -首先使用`use`语句来将`std::env`模块引入作用域。当函数嵌套了多于一层模块时,比如说`std::env::args`,通常使用`use`将父模块引入作用域,而不是引入其本身。`env::args`比单独的`args`要明确一些。当然,如果使用了多于一个`std::env`中的函数时,我们也只需要一个`use`语句。 +首先使用`use`语句来将`std::env`模块引入作用域以便可以使用它的`args`函数。注意`std::env::args`函数嵌套进了两层模块中。如第七章讲到的,当所需函数嵌套了多于一层模块时,通常将父模块引入作用域,而不是其自身。这便于我们利用`std::env`中的其他函数。这比增加了`use std::env::args;`后仅仅使用`args`调用函数要更明确一些;这样看起来好像一个定义于当前模块的函数。 -在`main`函数的第一行,我们调用了`env::args`,并立即使用`collect`来创建了一个 vector。这里我们也显式的注明了`args`的类型:`collect`可以被用来创建很多类型的集合。Rust 并不能推断出我们需要什么类型,所以类型注解是必须的。在 Rust 中我们很少会需要注明类型,不过`collect`是就一个通常需要这么做的函数。 + + + + +> 注意:`std::env::args`在其任何参数包含无效 Unicode 字符时会 panic。如果你需要接受包含无效 Unicode 字符的参数,使用`std::env::args_os`代替。这个函数返回`OsString`值而不是`String`值。出于简单考虑这里使用`std::env::args`,因为`OsString`值每个平台都不一样而且比`String`值处理起来更复杂。 + + + + + + +在`main`函数的第一行,我们调用了`env::args`,并立即使用`collect`来创建了一个包含迭代器所有值的 vector。`collect`可以被用来创建很多类型的集合,所以这里显式注明的`args`类型来指定我们需要一个字符串 vector。虽然在 Rust 中我们很少会需要注明类型,`collect`就是一个经常需要注明类型的函数,因为 Rust 不能推断出你想要什么类型的集合。 最后,我们使用调试格式`:?`打印出 vector。让我们尝试不用参数运行代码,接着用两个参数: @@ -44,50 +78,58 @@ $ cargo run needle haystack ["target/debug/greprs", "needle", "haystack"] ``` -你会注意一个有趣的事情:二进制文件的名字是第一个参数。其原因超出了本章介绍的范围,不过这是我们必须记住的。 + + -现在我们有了一个访问所有参数的方法,让我们如列表 12-2 中所示将需要的变量存放到变量中: +你可能注意到了 vector 的第一个值是"target/debug/greprs",它是二进制我呢见的名称。其原因超出了本章介绍的范围,不过需要记住的是我们保存了所需的两个参数。 + +### 将参数值保存进变量 + +打印出参数 vector 中的值仅仅展示了可以访问程序中指定为命令行参数的值。但是这并不是我们想要做的,我们希望将这两个参数的值保存进变量这样就可以在程序使用这些值。让我们如列表 12-2 这样做: + + + Filename: src/main.rs -```rust,ignore +```rust,should_panic use std::env; fn main() { let args: Vec = env::args().collect(); - let search = &args[1]; + let query = &args[1]; let filename = &args[2]; - println!("Searching for {}", search); + println!("Searching for {}", query); println!("In file {}", filename); } ``` -Listing 12-2: Create variables to hold the search -argument and filename argument +Listing 12-2: Create variables to hold the query argument and filename argument -记住,程序名称是是第一个参数,所以并不需要`args[0]`。我们决定从第一个参数将是需要搜索的字符串,所以将第一个参数的引用放入变量`search`中。第二个参数将是文件名,将其放入变量`filename`中。再次尝试运行程序: +正如我们在打印出 vector 时所看到的,程序的名称占据了 vector 的第一个值`args[0]`,所以我们从索引`1`开始。第一个参数`greprs`是需要搜索的字符串,所以将其将第一个参数的引用存放在变量`query`中。第二个参数将是文件名,所以将第二个参数的引用放入变量`filename`中。 + +我们将临时打印出出这些变量的值,再一次证明代码如我们期望的那样工作。让我们使用参数`test`和`sample.txt`再次运行这个程序: ``` $ cargo run test sample.txt Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs - Running `target\debug\greprs.exe test sample.txt` + Running `target/debug/greprs 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 a0e0933..2e10185 100644 --- a/src/ch12-02-reading-a-file.md +++ b/src/ch12-02-reading-a-file.md @@ -2,13 +2,13 @@ > [ch12-02-reading-a-file.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-02-reading-a-file.md) >
-> commit c49e5ee8859f8eb8f8867cbeafbdf5b802aa5894 +> commit b8e4fcbf289b82c12121b282747ce05180afb1fb -现在有了一些包含我们需要的信息的变量了,让我们试着使用他们。下一步目标是打开需要搜索的文件。为此,我需要一个文件。在项目的根目录创建一个文件`poem.txt`,并写入一些艾米莉·狄金森(Emily Dickinson)的诗: +接下来我们将读取由命令行文件名参数指定的文件。首先,需要一个用来测试的示例文件——用来确保`greprs`正常工作的最好的文件是拥有少量文本和多个行且有一些重复单词的文件。列表 12-3 是一首艾米莉·狄金森(Emily Dickinson)的诗,它正适合这个工作!在项目根目录创建一个文件`poem.txt`,并输入诗 "I'm nobody! Who are you?": Filename: poem.txt -``` +```text I'm nobody! Who are you? Are you nobody, too? Then there's a pair of us — don't tell! @@ -20,16 +20,21 @@ To tell your name the livelong day To an admiring bog! ``` +Listing 12-3: The poem "I'm nobody! Who are you?" by +Emily Dickinson that will make a good test case + + + -创建完这个文件后,让我们编辑 *src/main.rs* 并增加如列表 12-3 所示用来打开文件的代码: +创建完这个文件之后,修改 *src/main.rs* 并增加如列表 12-4 所示的打开文件的代码: Filename: src/main.rs -```rust,ignore +```rust,should_panic use std::env; use std::fs::File; use std::io::prelude::*; @@ -37,10 +42,10 @@ use std::io::prelude::*; fn main() { let args: Vec = env::args().collect(); - let search = &args[1]; + let query = &args[1]; let filename = &args[2]; - println!("Searching for {}", search); + println!("Searching for {}", query); println!("In file {}", filename); let mut f = File::open(filename).expect("file not found"); @@ -52,21 +57,22 @@ fn main() { } ``` -Listing 12-3: Read the contents of the file specified by -the second argument +Listing 12-4: Reading 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。 +首先,增加了更多的`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`将会放置它读取到的数据地方。最后,我们打印出了整个文件的内容,这是一个确认目前为止的程序能够工作的方法。 +在`main`中,我们增加了三点内容:第一,通过传递变量`filename`的值调用`File::open`函数的值来获取文件的可变句柄。创建了叫做`contents`的变量并将其设置为一个可变的,空的`String`。它将会存放之后读取的文件的内容。第三,对文件句柄调用`read_to_string`并传递`contents`的可变引用作为参数。 -尝试运行这些代码,随意指定第一个参数(因为还未实现搜索功能的部分)而将 *poem.txt* 文件将作为第二个参数: +在这些代码之后,我们再次增加了临时的`println!`打印出读取文件后`contents`的值,这样就可以检查目前为止的程序能否工作。 + +尝试运行这些代码,随意指定一个字符串作为第一个命令行参数(因为还未实现搜索功能的部分)而将 *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` + Running `target/debug/greprs the poem.txt` Searching for the In file poem.txt With text: @@ -81,4 +87,4 @@ To tell your name the livelong day To an admiring bog! ``` -好的!我们的代码可以工作了!然而,它还有一些瑕疵。因为程序还很小,这些瑕疵并不是什么大问题,不过随着程序功能的丰富,将会越来越难以用简单的方法修复他们。现在就让我们开始重构而不是等待之后处理。重构在只有少量代码时会显得容易得多。 \ No newline at end of file +好的!代码读取并打印出了文件的内容。虽然它还有一些瑕疵:`main`函数有着多个功能,同时也没有处理可能出现的错误。虽然我们的程序还很小,这些瑕疵并不是什么大问题。不过随着程序功能的丰富,将会越来越难以用简单的方法修复他们。在开发程序时,及早开始重构是一个最佳实践,因为重构少量代码时要容易的多,所以让我们现在就开始吧。 diff --git a/src/ch12-03-improving-error-handling-and-modularity.md b/src/ch12-03-improving-error-handling-and-modularity.md index 93a011e..3f1eebc 100644 --- a/src/ch12-03-improving-error-handling-and-modularity.md +++ b/src/ch12-03-improving-error-handling-and-modularity.md @@ -2,20 +2,28 @@ > [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 bdab3f38da5b7bf7277bfe21ec59a7a81880e6b4 +> commit b8e4fcbf289b82c12121b282747ce05180afb1fb -为了完善我们程序有四个问题需要修复,而他们都与潜在的错误和程序结构有关。第一个问题是在哪打开文件:我们使用了`expect`来在打开文件失败时指定一个错误信息,不过这个错误信息只是说“文件不存在”。还有很多打开文件失败的方式,不过我们总是假设是由于缺少文件导致的。例如,文件存在但是没有打开它的权限:这时,我们就打印出了错误不符合事实的错误信息! +为了改善我们的程序这里有四个问题需要修复,而且他们都与程序的组织方式和如何处理潜在错误有关。 -第二,我们不停的使用`expect`,这就有点类似我们之前在不传递任何命令行参数时索引会`panic!`时注意到的问题:这虽然时_可以工作_的,不过这有点没有原则性,而且整个程序中都需要他们,将错误处理都置于一处则会显得好很多。 +第一,`main`现在进行了两个任务:它解析了参数并打开了文件。对于一个这样的小函数,这并不是一个大问题。然而如果`main`中的功能持续增加,`main`函数处理的单独的任务也会增加。当函数承担了更多责任,它就更难以推导,更难以测试,并且更难以在不破坏其他部分的情况下做出修改。最好能分离出功能这样每个函数就负责一个任务。 -第三个问题是`main`函数现在处理两个工作:解析参数,并打开文件。对于一个小的函数来说,这不是什么大问题。然而随着程序中的`main`函数不断增长,`main`函数中独立的任务也会越来越多。因为一个函数拥有很多职责,它将难以理解、难以测试并难以在不破坏其他部分的情况下做出修改。 +这同时也关系到第二个问题:`search`和`filename`是程序中的配置变量,而像`f`和`contents`则用来执行程序逻辑。随着`main`函数的增长,就需要引入更多的变量到作用域中,而当作用域中有更多的变量时,将更难以追踪每个变量的目的。最好能将将配置变量组织进一个结构这样就能使他们的目的更明确了。 -这也关系到我们的第四个问题:`search`和`filename`是程序中配置性的变量,而像`f`和`contents`则用来执行程序逻辑。随着`main`函数增长,将引入更多的变量到作用域中,而当作用域中有更多的变量,将更难以追踪哪个变量用于什么目的。如果能够将配置型变量组织进一个结构就能使他们的目的更明确了。 +第三个问题是如果打开文件失败我们使用`expect`来打印出错误信息,不过这个错误信息只是说`file not found`。除了缺少文件之外还有很多打开文件可能失败的方式:例如,文件可能存在,不过可能没有打开它的权限。如果我们现在就出于这种情况,打印出的`file not found`错误信息就给了用户一个不符合事实的建议! -让我们重新组织程序来解决这些问题。 +第四,我们不停的使用`expect`来处理不同的错误,如果用户没有指定足够的参数来运行程序,他们会从 Rust 得到 "index out of bounds" 错误而这并不能明确的解释问题。如果所有的错误处理都位于一处这样将来的维护者在需要修改错误处理逻辑时就只需要咨询一处代码。将所有的错误处理都放在一处也有助于确保我们打印的错误信息对终端用户来说是有意义的。 + +让我们通过重构项目来解决这些问题。 ### 二进制项目的关注分离 + + + + + + 这类项目组织上的问题在很多相似类型的项目中很常见,所以 Rust 社区开发出一种关注分离的组织模式。这种模式可以用来组织任何用 Rust 构建的二进制项目,所以可以证明应该更早的开始这项重构,以为我们的项目符合这个模式。这个模式看起来像这样: 1. 将程序拆分成 *main.rs* 和 *lib.rs*。