From 8db776f83a89dcd0612979fa33d7a85c2101067b Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 29 May 2017 23:01:54 +0800 Subject: [PATCH 01/59] Update ch17-03-oo-design-patterns.md --- src/ch17-03-oo-design-patterns.md | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/ch17-03-oo-design-patterns.md b/src/ch17-03-oo-design-patterns.md index 5e65df5..544848c 100644 --- a/src/ch17-03-oo-design-patterns.md +++ b/src/ch17-03-oo-design-patterns.md @@ -1,27 +1,13 @@ ## 面向对象设计模式的实现 -让我们看一下状态设计模式和怎样在Rust中来使用它的例子. *状态模式*就是当一个值有多个内部状态时,值的行为改变基于内部状态. Let's look at an example of the state design pattern and how to use it in Rust. -The *state pattern* is when a value has some internal state, and the value's -behavior changes based on the internal state. The internal state is represented -by a set of objects that inherit shared functionality (we'll use structs and -traits since Rust doesn't have objects and inheritance). Each state object is -responsible for its own behavior and the rules for when it should change into -another state. The value that holds one of these state objects doesn't know -anything about the different behavior of the states or when to transition -between states. In the future when requirements change, we won't need to change -the code of the value holding the state or the code that uses the value. We'll -only need to update the code inside one of the state objects to change its -rules, or perhaps add more state objects. +让我们看一下状态设计模式和怎样在Rust中来使用它的例子. *状态模式*就是当一个值有多个内部状态时,值的行为改变基于内部状态. 内部状态被表示成一个对象集合, 集合中对象继承了一些共享功能(因为Rust中没有对象和继承, 所以我们将使用结构和trait). 每一个状态对象用于表征它自己的行为和当它应该改变成另一种状态时的状态改变规则. 模式中的值持有这些状态对象中的某个对象, 它并不了解这些状态的行为有什么不同, 它也不明白什么时候在状态间进行转换. 将来当需要改变的时候, 我们将不需要改变模式中的值持有状态的代码, 也不需要改变使用模式中的值的代码. 我们只需要更新状态对象中的某个对象内部的代码来改变它的规则, 或者也许还有添加更多的状态对象. -In order to explore this idea, we're going to implement a blog post workflow in -an incremental way. The workflow that we want our blog posts to follow, once -we're done with the implementation, is: +为了探究这个想法, 我们将通过一个增量方法来实现一个博客提交工作流. 一旦我们实现了这个工作流, 我们想我们的博客按如下方式进行提交: -1. A blog post starts as an empty draft. -2. Once the draft is done, we request a review of the post. -3. Once the post is approved, it gets published. -4. Only published blog posts return content to print so that we can't - accidentally print the text of a post that hasn't been approved. +1. 一个博客提交始于一个空草稿. +2. 一旦草稿被写完, 我们就提交并请求审查. +3. 一旦提交被审核通过, 它就被发布. +4. 在所有的博客中, 只有被发布的博客才返回它的内容, 所以我们不能显示未被审核的博客的内容. Any other changes attempted on a post should have no effect. For example, if we try to approve a draft blog post before we've requested a review, the post From aec4f1ccd5083fbef272014af794bac65909cd73 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Tue, 30 May 2017 23:40:16 +0800 Subject: [PATCH 02/59] Update ch17-03-oo-design-patterns.md --- src/ch17-03-oo-design-patterns.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ch17-03-oo-design-patterns.md b/src/ch17-03-oo-design-patterns.md index 544848c..e09d444 100644 --- a/src/ch17-03-oo-design-patterns.md +++ b/src/ch17-03-oo-design-patterns.md @@ -9,12 +9,9 @@ 3. 一旦提交被审核通过, 它就被发布. 4. 在所有的博客中, 只有被发布的博客才返回它的内容, 所以我们不能显示未被审核的博客的内容. -Any other changes attempted on a post should have no effect. For example, if we -try to approve a draft blog post before we've requested a review, the post -should stay an unpublished draft. +任何其它的对一个提交上来的博客的更改都不会生效. 例如, 如果我们想在审核一个博客的草稿之前就把它批准通过, 那么这个提交上来的博客将仍然将停留在未发布的草稿状态. -Listing 17-11 shows this workflow in code form. This is an example usage of the -API we're going to implement in a library crate named `blog`: +17-11中以代码的形式罗列展示了这个工作流. 这是一个我们将实现在一个名叫`blog`的crate库中的API的用法的例子: Filename: src/main.rs From bdfbc62274b5b36e4196a8e9c3e6783abc56d821 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Tue, 30 May 2017 23:48:08 +0800 Subject: [PATCH 03/59] Update ch17-03-oo-design-patterns.md --- src/ch17-03-oo-design-patterns.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ch17-03-oo-design-patterns.md b/src/ch17-03-oo-design-patterns.md index e09d444..18d7cd6 100644 --- a/src/ch17-03-oo-design-patterns.md +++ b/src/ch17-03-oo-design-patterns.md @@ -11,7 +11,7 @@ 任何其它的对一个提交上来的博客的更改都不会生效. 例如, 如果我们想在审核一个博客的草稿之前就把它批准通过, 那么这个提交上来的博客将仍然将停留在未发布的草稿状态. -17-11中以代码的形式罗列展示了这个工作流. 这是一个我们将实现在一个名叫`blog`的crate库中的API的用法的例子: +列表17-11中以代码的形式展示了这个工作流. 这是一个我们将实现在一个名叫`blog`的crate库中的API的用法的例子: Filename: src/main.rs @@ -33,8 +33,7 @@ fn main() { } ``` -Listing 17-11: Code that demonstrates the desired -behavior we want our `blog` crate to have +列表17-11: 展示我们希望我们的`blog`crate所拥有的行为的代码 We want to be able to create a new draft blog post with `Post::new`. Then, we want to add some text to the blog post while we're in the draft state. If we From 765870c9e3cb86aa1d0376c5e35eb3275fa0b5fe Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 5 Jun 2017 15:52:37 +0800 Subject: [PATCH 04/59] Update ch17-03-oo-design-patterns.md --- src/ch17-03-oo-design-patterns.md | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/ch17-03-oo-design-patterns.md b/src/ch17-03-oo-design-patterns.md index 18d7cd6..221e588 100644 --- a/src/ch17-03-oo-design-patterns.md +++ b/src/ch17-03-oo-design-patterns.md @@ -35,25 +35,11 @@ fn main() { 列表17-11: 展示我们希望我们的`blog`crate所拥有的行为的代码 -We want to be able to create a new draft blog post with `Post::new`. Then, we -want to add some text to the blog post while we're in the draft state. If we -try to print out the post's content immediately, though, we shouldn't get any -text, since the post is still a draft. We've added an `assert_eq!` here for -demonstration purposes. Asserting that a draft blog post returns an empty -string from the `content` method would make an excellent unit test in our -library, but we're not going to write tests for this example. +我们希望用`Post::new`来创建一个新的博客草稿. 然后, 我们想在草稿状态的时候把一些文字添加到博客中. 如果我们想直接把博客的内容打印出来, 我们将不会得到任何文字, 因为该博客仍然是一个草稿. 我们在这里加了一个`assert_eq`是用于演示目的. 断言一个提交的博客草稿从`content`方法返回一个空字符串可以在我们的库中提供出色的单元测试, 但是我们将不会为这个例子写测试. -Next, we want to be able to request a review of our post, and `content` should -still return an empty string while waiting for a review. Lastly, when we -approve the blog post, it should get published, which means the text we added -will be returned when we call `content`. +然后, 我们希望能够请求审查我们提交的博客, 在等待审查期间`content`应该仍然返回一个空字符串. 最后当我们审查通过这个提交的博客后, 它应该被发布, 也就是说当我们调用`content`时, 刚才被我们添加的文字回被返回. -Notice that the only type we're interacting with from the crate is the `Post` -type. The various states a post can be in (draft, waiting for review, -published) are managed internally to the `Post` type. The states change due to -the methods we call on the `Post` instance, but we don't have to manage the -state changes directly. This also means we won't make a mistake with the -states, like forgetting to request a review before publishing. +注意我们和crate联系的唯一类型是`Post`类型. 一个博客的状态可以是草稿、等待审核和发布中的一种, 这些状态在`Post`类型内部被管理. 状态通过我们调用`Post`实例上的方法被改变, 我们不必直接去改变状态. 这也意味着我们不会用状态来犯错误, 比如在发布之前忘了请求审核. ### Defining `Post` and Creating a New Instance in the Draft State From 90166696f482771fe9ec27e779c5cbdb3e23aad6 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 5 Jun 2017 15:58:37 +0800 Subject: [PATCH 05/59] Update ch17-03-oo-design-patterns.md --- src/ch17-03-oo-design-patterns.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ch17-03-oo-design-patterns.md b/src/ch17-03-oo-design-patterns.md index 221e588..594b301 100644 --- a/src/ch17-03-oo-design-patterns.md +++ b/src/ch17-03-oo-design-patterns.md @@ -41,9 +41,9 @@ fn main() { 注意我们和crate联系的唯一类型是`Post`类型. 一个博客的状态可以是草稿、等待审核和发布中的一种, 这些状态在`Post`类型内部被管理. 状态通过我们调用`Post`实例上的方法被改变, 我们不必直接去改变状态. 这也意味着我们不会用状态来犯错误, 比如在发布之前忘了请求审核. -### Defining `Post` and Creating a New Instance in the Draft State +### 定义`Post`类型并创建一个草稿状态的新实例 -Let's get started on the implementation of the library! We know we want to have +让我们开始来实现这个库吧! We know we want to have a public `Post` struct that holds some content, so let's start with the definition of the struct and an associated public `new` function to create an instance of `Post` as shown in Listing 17-12. We're also going to have a From 42c9b071f82289d10e3d547234daa278e2a6b139 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 5 Jun 2017 23:20:57 +0800 Subject: [PATCH 06/59] Update ch17-03-oo-design-patterns.md --- src/ch17-03-oo-design-patterns.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/ch17-03-oo-design-patterns.md b/src/ch17-03-oo-design-patterns.md index 594b301..d26b91b 100644 --- a/src/ch17-03-oo-design-patterns.md +++ b/src/ch17-03-oo-design-patterns.md @@ -37,23 +37,13 @@ fn main() { 我们希望用`Post::new`来创建一个新的博客草稿. 然后, 我们想在草稿状态的时候把一些文字添加到博客中. 如果我们想直接把博客的内容打印出来, 我们将不会得到任何文字, 因为该博客仍然是一个草稿. 我们在这里加了一个`assert_eq`是用于演示目的. 断言一个提交的博客草稿从`content`方法返回一个空字符串可以在我们的库中提供出色的单元测试, 但是我们将不会为这个例子写测试. -然后, 我们希望能够请求审查我们提交的博客, 在等待审查期间`content`应该仍然返回一个空字符串. 最后当我们审查通过这个提交的博客后, 它应该被发布, 也就是说当我们调用`content`时, 刚才被我们添加的文字回被返回. +然后, 我们希望能够请求审查我们提交的博客, 在等待审查期间`content`应该仍然返回一个空字符串. 最后当我们审核通过这个提交的博客后, 它应该被发布, 也就是说当我们调用`content`时, 刚才被我们添加的文字回被返回. 注意我们和crate联系的唯一类型是`Post`类型. 一个博客的状态可以是草稿、等待审核和发布中的一种, 这些状态在`Post`类型内部被管理. 状态通过我们调用`Post`实例上的方法被改变, 我们不必直接去改变状态. 这也意味着我们不会用状态来犯错误, 比如在发布之前忘了请求审核. ### 定义`Post`类型并创建一个草稿状态的新实例 -让我们开始来实现这个库吧! We know we want to have -a public `Post` struct that holds some content, so let's start with the -definition of the struct and an associated public `new` function to create an -instance of `Post` as shown in Listing 17-12. We're also going to have a -private trait `State`. `Post` will hold a trait object of `Box` inside -an `Option` in a private field named `state`. We'll see why the `Option` is -necessary in a bit. The `State` trait defines all the behavior different post -states share, and the `Draft`, `PendingReview`, and `Published` states will all -implement the `State` trait. For now, the trait does not have any methods, and -we're going to start by defining just the `Draft` state since that's the state -we want to start in: +让我们开始来实现这个库吧! 我们知道我们想有一个持有某些内容的公有的`Post`结构, 所以让我们从这个结构的定义和这个结构的一个公有的关联函数`new`开始, 这个`new`函数会创建如列表17-12中所示的`Post`实例. 我们也将有一个名叫`State`的私有的trait. `Post`将有一个私有的`state`属性, 该属性是一个`Option`, 它会持有一个`Box`类型的trait对象. 等会儿我们还将看到为什么`Option`是必要的. 这个`State`trait定义了不同的提交上来的博客的状态会共享的所有行为, 因此`Draft`、 `PendingReview`和`Published`状态都将会实现`State`trait. 目前为止, 这个trait还没有一个方法, 我们将从定义`Draft`状态开始, 因为它是我们想要的起始状态: Filename: src/lib.rs From 6fc175d16ba1ccb4767b7eb2984b5a1b716d3ce1 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 7 Jun 2017 18:01:14 +0800 Subject: [PATCH 07/59] Update ch17-03-oo-design-patterns.md --- src/ch17-03-oo-design-patterns.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ch17-03-oo-design-patterns.md b/src/ch17-03-oo-design-patterns.md index d26b91b..6e4f567 100644 --- a/src/ch17-03-oo-design-patterns.md +++ b/src/ch17-03-oo-design-patterns.md @@ -69,15 +69,9 @@ struct Draft {} impl State for Draft {} ``` -Listing 17-12: Definition of a `Post` struct and a `new` -function that creates a new `Post` instance, a `State` trait, and a `Draft` -struct that implements `State` +列表17-12: 定义一个`Post`结构, 一个`Post`结构的创建一个新的`Post`实例的关联函数`new`, 一个`State`trait, 和一个实现了`State`的结构`Draft`. -When we create a new `Post`, we set its `state` field to a `Some` value holding -a `Box` pointing to a new instance of the `Draft` struct. This ensures whenever -we create a new instance of `Post`, it'll start out as a draft. Because the -`state` field of `Post` is private, there's no way to create a `Post` in any -other state! +当我们创建一个新的`Post`实例时, 我们把它的`state`字段设为一个持有指向一个新的`Draft`结构实例的`Box`的`Some`类型的值. 这就确保无论何时我们创建一个`Post`类型的新实例时, 它总是处于草稿状态. 因为`Post`的`state`字段是私有的, 也就没有办法创建一个处于其它状态的`Post`! ### Storing the Text of the Post Content From 72e6b9b860e3aa7687128c055eb3d3f6096a9ea0 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 7 Jun 2017 18:05:20 +0800 Subject: [PATCH 08/59] Update ch17-03-oo-design-patterns.md --- src/ch17-03-oo-design-patterns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch17-03-oo-design-patterns.md b/src/ch17-03-oo-design-patterns.md index 6e4f567..030b79d 100644 --- a/src/ch17-03-oo-design-patterns.md +++ b/src/ch17-03-oo-design-patterns.md @@ -71,7 +71,7 @@ impl State for Draft {} 列表17-12: 定义一个`Post`结构, 一个`Post`结构的创建一个新的`Post`实例的关联函数`new`, 一个`State`trait, 和一个实现了`State`的结构`Draft`. -当我们创建一个新的`Post`实例时, 我们把它的`state`字段设为一个持有指向一个新的`Draft`结构实例的`Box`的`Some`类型的值. 这就确保无论何时我们创建一个`Post`类型的新实例时, 它总是处于草稿状态. 因为`Post`的`state`字段是私有的, 也就没有办法创建一个处于其它状态的`Post`! +当我们创建一个新的`Post`实例时, 我们把它的`state`字段设为一个`Some`类型的值, 该值持有指向一个新的`Draft`结构实例的`Box`. 这就确保无论何时我们创建一个`Post`类型的新实例时, 它总是处于草稿状态. 因为`Post`的`state`字段是私有的, 除了调用`new`无法创建一个处于其它状态的`Post`! ### Storing the Text of the Post Content From 209019a508616fd28a2edc86c8c24e8d45ddd32e Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 7 Jun 2017 18:08:13 +0800 Subject: [PATCH 09/59] Update ch17-03-oo-design-patterns.md --- src/ch17-03-oo-design-patterns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch17-03-oo-design-patterns.md b/src/ch17-03-oo-design-patterns.md index 030b79d..3893248 100644 --- a/src/ch17-03-oo-design-patterns.md +++ b/src/ch17-03-oo-design-patterns.md @@ -73,7 +73,7 @@ impl State for Draft {} 当我们创建一个新的`Post`实例时, 我们把它的`state`字段设为一个`Some`类型的值, 该值持有指向一个新的`Draft`结构实例的`Box`. 这就确保无论何时我们创建一个`Post`类型的新实例时, 它总是处于草稿状态. 因为`Post`的`state`字段是私有的, 除了调用`new`无法创建一个处于其它状态的`Post`! -### Storing the Text of the Post Content +### 存放Post的文本内容 In the `Post::new` function, we set the `content` field to a new, empty `String`. In Listing 17-11, we showed that we want to be able to call a method From 311f004949e1a62a00a55d0529c06954f491374e Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Thu, 8 Jun 2017 16:33:47 +0800 Subject: [PATCH 10/59] Update ch17-03-oo-design-patterns.md --- src/ch17-03-oo-design-patterns.md | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/src/ch17-03-oo-design-patterns.md b/src/ch17-03-oo-design-patterns.md index 3893248..75ff2a8 100644 --- a/src/ch17-03-oo-design-patterns.md +++ b/src/ch17-03-oo-design-patterns.md @@ -75,14 +75,7 @@ impl State for Draft {} ### 存放Post的文本内容 -In the `Post::new` function, we set the `content` field to a new, empty -`String`. In Listing 17-11, we showed that we want to be able to call a method -named `add_text` and pass a `&str` to it to add that text to the content of the -blog post. We're choosing to implement this as a method rather than exposing -the `content` field as `pub` because we want to be able to control how the -`content` field's data is read by implementing a method later. The `add_text` -method is pretty straightforward though, let's add the implementation in -Listing 17-13 to the `impl Post` block: +在`Post::new`函数中, 我们把`content`字段设为一个新的空字符串. 在列表17-11中, 我们演示我们想能调用一个`add_text`方法, 通过传递一个`&str`到这个方法中来把文字添加到博客的内容中. 我们选择把这个实现为一个方法而不是把`content`暴露成一个`pub`型的字段是因为稍后我们想实现一个方法来控制对`content`字段内容的读取. `add_text`方法很容易理解, 让我们在列表17-13中来把它的实现添加到`impl Post`块中: Filename: src/lib.rs @@ -99,22 +92,13 @@ impl Post { } ``` -Listing 17-13: Implementing the `add_text` method to add -text to a post's `content` +列表17-13: 实现`add_text`方法来把文字添加到博客的`content`方法中 -`add_text` takes a mutable reference to `self`, since we're changing the `Post` -instance that we're calling `add_text` on. We then call `push_str` on the -`String` in `content` and pass the `text` argument to add to the saved -`content`. This isn't part of the state pattern since its behavior doesn't -depend on the state that the post is in. The `add_text` method doesn't interact -with the `state` field at all, but it is part of the behavior we want to -support. +因为当调用`add_text`方法时, 我们将会改变`Post`实例, 所以我们用一个可变的`self`引用作为参数. 然后我们调用`content`的`String`类型上的`push_str`方法, 传入`text`参数来把它添加到调用方法的实例的`content`上. 这不是状态模式的一部分, 因为它的行为并不依赖博客所处的状态. `add_text`方法根本不与`state`字段交互, 但它是我们想提供的功能的一部分. -### Content of a Draft Post is Empty +### 草稿博客的内容为空 -After we've called `add_text` and added some content to our post, we still want -the `content` method to return an empty string slice since the post is still in -the draft state, as shown on line 8 of Listing 17-11. For now, let's implement +列表17-11的第八行显示, 在我们调用`add_text`把某些内容添加到博客后, 当博客仍然处于草稿状态时, 我们仍然希望`content`方法返回一个空字符串切片. For now, let's implement the `content` method with the simplest thing that will fulfill this requirement: always returning an empty string slice. We're going to change this later once we implement the ability to change a post's state to be published. With what we From e67b468860d49d02872488dbea3fa0e317ce8c01 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Fri, 9 Jun 2017 23:03:51 +0800 Subject: [PATCH 11/59] Update ch17-03-oo-design-patterns.md --- src/ch17-03-oo-design-patterns.md | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/ch17-03-oo-design-patterns.md b/src/ch17-03-oo-design-patterns.md index 75ff2a8..5c2fc3e 100644 --- a/src/ch17-03-oo-design-patterns.md +++ b/src/ch17-03-oo-design-patterns.md @@ -98,13 +98,7 @@ impl Post { ### 草稿博客的内容为空 -列表17-11的第八行显示, 在我们调用`add_text`把某些内容添加到博客后, 当博客仍然处于草稿状态时, 我们仍然希望`content`方法返回一个空字符串切片. For now, let's implement -the `content` method with the simplest thing that will fulfill this requirement: -always returning an empty string slice. We're going to change this later once -we implement the ability to change a post's state to be published. With what we -have so far, though, posts can only be in the draft state, which means the post -content should always be empty. Listing 17-14 shows this placeholder -implementation: +列表17-11的第八行显示, 在我们调用`add_text`把某些内容添加到博客后, 当博客仍然处于草稿状态时, 我们仍然希望`content`方法返回一个空字符串切片. 现在, 让我们用最简单的办法来实现完全满足需求的`content`方法: 永远返回一个空的字符串切片. 稍后等我们实现了把提交上来的博客的状态变成已发布状态的功能后, 我们再来修改这段代码. 就目前的情况来看, 提交的博客只处于草稿状态, 也就是说提交的内容永远都为空. 列表17-14显示了这种占位符式的实现代码: Filename: src/lib.rs @@ -121,16 +115,13 @@ impl Post { } ``` -Listing 17-14: Adding a placeholder implementation for -the `content` method on `Post` that always returns an empty string slice +列表17-14: 为`Post`的`content`方法加入一个占位符实现, 让它永远都返回一个空的字符串切片 -With this added `content` method, everything in Listing 17-11 up to line 8 -works as we intend. +通过加入`content`方法, 在列表17-11中的第八行以前的所以东西都可以如我们期望的那样运行了. -### Requesting a Review of the Post Changes its State +### 请求审核博客来改变它的状态 -Next up is requesting a review of a post, which should change its state from -`Draft` to `PendingReview`. We want `post` to have a public method named +接着就是请求审核博客了, 这会把博客的状态从`Draft`变成`PendingReview`. We want `post` to have a public method named `request_review` that will take a mutable reference to `self`. Then we're going to call an internal `request_review` method on the state that we're holding, and this second `request_review` method will consume the current state and return a From 4c343eff46e7b28afd779418ed3523016f205e59 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Sun, 11 Jun 2017 23:09:30 +0800 Subject: [PATCH 12/59] Update ch17-03-oo-design-patterns.md --- src/ch17-03-oo-design-patterns.md | 342 ++++++++---------------------- 1 file changed, 88 insertions(+), 254 deletions(-) diff --git a/src/ch17-03-oo-design-patterns.md b/src/ch17-03-oo-design-patterns.md index 5c2fc3e..777cf2b 100644 --- a/src/ch17-03-oo-design-patterns.md +++ b/src/ch17-03-oo-design-patterns.md @@ -1,19 +1,23 @@ ## 面向对象设计模式的实现 -让我们看一下状态设计模式和怎样在Rust中来使用它的例子. *状态模式*就是当一个值有多个内部状态时,值的行为改变基于内部状态. 内部状态被表示成一个对象集合, 集合中对象继承了一些共享功能(因为Rust中没有对象和继承, 所以我们将使用结构和trait). 每一个状态对象用于表征它自己的行为和当它应该改变成另一种状态时的状态改变规则. 模式中的值持有这些状态对象中的某个对象, 它并不了解这些状态的行为有什么不同, 它也不明白什么时候在状态间进行转换. 将来当需要改变的时候, 我们将不需要改变模式中的值持有状态的代码, 也不需要改变使用模式中的值的代码. 我们只需要更新状态对象中的某个对象内部的代码来改变它的规则, 或者也许还有添加更多的状态对象. +> [ch17-03-oo-design-patterns.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch17-03-oo-design-patterns.md) +>
+> commit 67737ff868e3347588cc832eceb8fc237afc5895 -为了探究这个想法, 我们将通过一个增量方法来实现一个博客提交工作流. 一旦我们实现了这个工作流, 我们想我们的博客按如下方式进行提交: +让我们看看一个状态设计模式的例子以及如何在 Rust 中使用他们。**状态模式**(*state pattern*)是指一个值有某些内部状态,而它的行为随着其内部状态而改变。内部状态由一系列继承了共享功能的对象表现(我们使用结构体和 trait 因为 Rust 没有对象和继承)。每一个状态对象负责它自身的行为和当需要改变为另一个状态时的规则。持有任何一个这种状态对象的值对于不同状态的行为以及何时状态转移毫不知情。当将来需求改变时,无需改变值持有状态或者使用值的代码。我们只需更新某个状态对象中的代码来改变它的规则,或者是增加更多的状态对象。 -1. 一个博客提交始于一个空草稿. -2. 一旦草稿被写完, 我们就提交并请求审查. -3. 一旦提交被审核通过, 它就被发布. -4. 在所有的博客中, 只有被发布的博客才返回它的内容, 所以我们不能显示未被审核的博客的内容. +为了探索这个概念,我们将实现一个增量式的发布博文的工作流。这个我们希望发布博文时所应遵守的工作流,一旦完成了它的实现,将为如下: -任何其它的对一个提交上来的博客的更改都不会生效. 例如, 如果我们想在审核一个博客的草稿之前就把它批准通过, 那么这个提交上来的博客将仍然将停留在未发布的草稿状态. +1. 博文从空白的草案开始。 +2. 一旦草案完成,请求审核博文。 +3. 一旦博文过审,它将被发表。 +4. 只有被发表的博文的内容会被打印,这样就不会意外打印出没有被审核的博文的文本。 -列表17-11中以代码的形式展示了这个工作流. 这是一个我们将实现在一个名叫`blog`的crate库中的API的用法的例子: +任何其他对博文的修改尝试都是没有作用的。例如,如果尝试在请求审核之前通过一个草案博文,博文应该保持未发布的状态。 -Filename: src/main.rs +列表 17-11 展示这个工作流的代码形式。这是一个我们将要在一个叫做 `blog` 的库 crate 中实现的 API 的使用示例: + +文件名: src/main.rs ```rust,ignore extern crate blog; @@ -33,19 +37,19 @@ fn main() { } ``` -列表17-11: 展示我们希望我们的`blog`crate所拥有的行为的代码 +列表 17-11: 展示了 `blog` crate 期望行为的代码 -我们希望用`Post::new`来创建一个新的博客草稿. 然后, 我们想在草稿状态的时候把一些文字添加到博客中. 如果我们想直接把博客的内容打印出来, 我们将不会得到任何文字, 因为该博客仍然是一个草稿. 我们在这里加了一个`assert_eq`是用于演示目的. 断言一个提交的博客草稿从`content`方法返回一个空字符串可以在我们的库中提供出色的单元测试, 但是我们将不会为这个例子写测试. +我们希望能够使用 `Post::new` 创建一个新的博文草案。接着希望能在草案阶段为博文编写一些文本。如果尝试立即打印出博文的内容,将不会得到任何文本,因为博文仍然是草案。这里增加的 `assert_eq!` 用于展示目的。断言草案博文的 `content` 方法返回空字符串将能作为库的一个非常好的单元测试,不过我们并不准备为这个例子编写单元测试。 -然后, 我们希望能够请求审查我们提交的博客, 在等待审查期间`content`应该仍然返回一个空字符串. 最后当我们审核通过这个提交的博客后, 它应该被发布, 也就是说当我们调用`content`时, 刚才被我们添加的文字回被返回. +接下来,我们希望能够请求审核博文,而在等待审核的阶段 `content` 应该仍然返回空字符串,当博文审核通过,它应该被发表,这意味着当调用 `content` 时我们编写的文本将被返回。 -注意我们和crate联系的唯一类型是`Post`类型. 一个博客的状态可以是草稿、等待审核和发布中的一种, 这些状态在`Post`类型内部被管理. 状态通过我们调用`Post`实例上的方法被改变, 我们不必直接去改变状态. 这也意味着我们不会用状态来犯错误, 比如在发布之前忘了请求审核. +注意我们与 crate 交互的唯一的类型是 `Post`。博文可能处于的多种状态(草案,等待审核和发布)由 `Post` 内部管理。博文状态依我们在`Post`调用的方法而改变,但不必直接管理状态改变。这也意味着不会在状态上犯错,比如忘记了在发布前请求审核。 -### 定义`Post`类型并创建一个草稿状态的新实例 +### 定义 `Post` 并新建一个草案状态的实例 -让我们开始来实现这个库吧! 我们知道我们想有一个持有某些内容的公有的`Post`结构, 所以让我们从这个结构的定义和这个结构的一个公有的关联函数`new`开始, 这个`new`函数会创建如列表17-12中所示的`Post`实例. 我们也将有一个名叫`State`的私有的trait. `Post`将有一个私有的`state`属性, 该属性是一个`Option`, 它会持有一个`Box`类型的trait对象. 等会儿我们还将看到为什么`Option`是必要的. 这个`State`trait定义了不同的提交上来的博客的状态会共享的所有行为, 因此`Draft`、 `PendingReview`和`Published`状态都将会实现`State`trait. 目前为止, 这个trait还没有一个方法, 我们将从定义`Draft`状态开始, 因为它是我们想要的起始状态: +让我们开始实现这个库吧!我们知道需要一个公有 `Post` 结构体来存放一些文本,所以让我们从结构体的定义和一个创建 `Post` 实例的公有关联函数 `new` 开始,如列表 17-12 所示。我们还需定义一个私有 trait `State`。`Post` 将在私有字段 `state` 中存放一个 `Option` 中的 trait 对象 `Box`。稍后将会看到为何 `Option` 是必须的。`State` trait 定义了所有不同状态的博文所共享的行为,同时 `Draft`、`PendingReview` 和 `Published` 状态都会实现`State` 状态。现在这个 trait 并没有任何方法,同时开始将只定义`Draft`状态因为这是我们希望开始的状态: -Filename: src/lib.rs +文件名: src/lib.rs ```rust pub struct Post { @@ -69,15 +73,16 @@ struct Draft {} impl State for Draft {} ``` -列表17-12: 定义一个`Post`结构, 一个`Post`结构的创建一个新的`Post`实例的关联函数`new`, 一个`State`trait, 和一个实现了`State`的结构`Draft`. +列表 17-12: `Post`结构体的定义和新建 `Post` 实例的 `new`函数,`State` trait 和实现了 `State` 的结构体 `Draft` -当我们创建一个新的`Post`实例时, 我们把它的`state`字段设为一个`Some`类型的值, 该值持有指向一个新的`Draft`结构实例的`Box`. 这就确保无论何时我们创建一个`Post`类型的新实例时, 它总是处于草稿状态. 因为`Post`的`state`字段是私有的, 除了调用`new`无法创建一个处于其它状态的`Post`! +当创建新的 `Post` 时,我们将其 `state` 字段设置为一个 `Some` 值,它存放了指向一个 `Draft` 结构体新实例的 `Box`。这确保了无论何时新建一个 `Post` 实例,它会从草案开始。因为 `Post` 的 `state` 字段是私有的,也就无法创建任何其他状态的 `Post` 了!。 -### 存放Post的文本内容 +### 存放博文内容的文本 -在`Post::new`函数中, 我们把`content`字段设为一个新的空字符串. 在列表17-11中, 我们演示我们想能调用一个`add_text`方法, 通过传递一个`&str`到这个方法中来把文字添加到博客的内容中. 我们选择把这个实现为一个方法而不是把`content`暴露成一个`pub`型的字段是因为稍后我们想实现一个方法来控制对`content`字段内容的读取. `add_text`方法很容易理解, 让我们在列表17-13中来把它的实现添加到`impl Post`块中: +在 `Post::new` 函数中,我们设置 `content` 字段为新的空 `String`。在列表 17-11 中,展示了我们希望能够调用一个叫做 `add_text` 的方法并向其传递一个 `&str` 来将文本增加到博文的内容中。选择实现为一个方法而不是将 `content` 字段暴露为 `pub` 是因为我们希望能够通过之后实现的一个方法来控制 `content` 字段如何被读取。`add_text` 方法是非常直观的,让我们在列表 17-13 的 `impl Post` 块中增加一个实现: -Filename: src/lib.rs + +文件名: src/lib.rs ```rust # pub struct Post { @@ -92,15 +97,15 @@ impl Post { } ``` -列表17-13: 实现`add_text`方法来把文字添加到博客的`content`方法中 +列表 17-13: 实现方法 `add_text` 来向博文的 `content` 增加文本 -因为当调用`add_text`方法时, 我们将会改变`Post`实例, 所以我们用一个可变的`self`引用作为参数. 然后我们调用`content`的`String`类型上的`push_str`方法, 传入`text`参数来把它添加到调用方法的实例的`content`上. 这不是状态模式的一部分, 因为它的行为并不依赖博客所处的状态. `add_text`方法根本不与`state`字段交互, 但它是我们想提供的功能的一部分. +`add_text` 获取一个 `self` 的可变引用,因为需要改变调用 `add_text` 的 `Post`。接着调用 `content` 中的 `String` 的 `push_str` 并传递 `text` 参数来保存到 `content` 中。这不是状态模式的一部分,因为它的行为并不依赖博文所处的状态。`add_text` 方法完全不与 `state` 状态交互,不过这是我们希望支持的行为的一部分。 -### 草稿博客的内容为空 +### 博文草案的内容是空的 -列表17-11的第八行显示, 在我们调用`add_text`把某些内容添加到博客后, 当博客仍然处于草稿状态时, 我们仍然希望`content`方法返回一个空字符串切片. 现在, 让我们用最简单的办法来实现完全满足需求的`content`方法: 永远返回一个空的字符串切片. 稍后等我们实现了把提交上来的博客的状态变成已发布状态的功能后, 我们再来修改这段代码. 就目前的情况来看, 提交的博客只处于草稿状态, 也就是说提交的内容永远都为空. 列表17-14显示了这种占位符式的实现代码: +调用 `add_text` 并像博文增加一些内容之后,我们仍然希望 `content` 方法返回一个空字符串 slice,因为博文仍然处于草案状态,如列表 17-11 的第 8 行所示。现在让我们使用能满足要求的最简单的方式来实现 `content` 方法 总是返回一个空字符 slice。当实现了将博文状态改为发布的能力之后将改变这一做法。但是现在博文只能是草案状态,这意味着其内容总是空的。列表 17-14 展示了这个占位符实现: -Filename: src/lib.rs +文件名: src/lib.rs ```rust # pub struct Post { @@ -115,24 +120,15 @@ impl Post { } ``` -列表17-14: 为`Post`的`content`方法加入一个占位符实现, 让它永远都返回一个空的字符串切片 +列表 17-14: 增加一个 `Post` 的 `content` 方法的占位实现,它总是返回一个空字符串 slice -通过加入`content`方法, 在列表17-11中的第八行以前的所以东西都可以如我们期望的那样运行了. +通过增加这个 `content`方法,列表 17-11 中直到第 8 行的代码能如期运行。 -### 请求审核博客来改变它的状态 +### 请求审核博文来改变其状态 -接着就是请求审核博客了, 这会把博客的状态从`Draft`变成`PendingReview`. We want `post` to have a public method named -`request_review` that will take a mutable reference to `self`. Then we're going -to call an internal `request_review` method on the state that we're holding, and -this second `request_review` method will consume the current state and return a -new state. In order to be able to consume the old state, the second `request_review` -method needs to take ownership of the state value. This is where the `Option` comes -in: we're going to `take` the `Some` value out of the `state` field and leave a -`None` in its place since Rust doesn't let us have unpopulated fields in -structs. Then we'll set the post's `state` value to the result of this -operation. Listing 17-15 shows this code: +接下来是请求审核博文,这应当将其状态由 `Draft` 改为 `PendingReview`。我们希望 `post` 有一个获取 `self` 可变引用的公有方法 `request_review`。接着将调用内部存放的状态的 `request_review` 方法,而这第二个 `request_review` 方法会消费当前的状态并返回要一个状态。为了能够消费旧状态,第二个 `request_review` 方法需要能够获取状态值的所有权。这就是 `Option` 的作用:我们将 `take` 字段 `state` 中的 `Some` 值并留下一个 `None` 值,因为 Rust 并不允许结构体中有空字段。接着将博文的 `state` 设置为这个操作的结果。列表 17-15 展示了这些代码: -Filename: src/lib.rs +文件名: src/lib.rs ```rust # pub struct Post { @@ -170,43 +166,21 @@ impl State for PendingReview { } ``` -Listing 17-15: Implementing `request_review` methods on -`Post` and the `State` trait +列表 17-15: 实现 `Post` 和 `State` trait 的 `request_review` 方法 -We've added the `request_review` method to the `State` trait; all types that -implement the trait will now need to implement the `request_review` method. -Note that rather than having `self`, `&self`, or `&mut self` as the first -parameter of the method, we have `self: Box`. This syntax means the -method is only valid when called on a `Box` holding the type. This syntax takes -ownership of `Box`, which is what we want because we're transforming the -old state into a new state, and we want the old state to no longer be valid. +这里给 `State` trait 增加了 `request_review` 方法;所有实现了这个 trait 的类型现在都需要实现 `request_review` 方法。注意不用于使用`self`、 `&self` 或者 `&mut self` 作为方法的第一个参数,这里使用了 `self: Box`。这个语法意味着这个方法调用只对这个类型的 `Box` 有效。这个语法获取了 `Box` 的所有权,这是我们希望的,因为需要从老状态转换为新状态,同时希望老状态不再有效。 -The implementation for the `request_review` method on `Draft` is to return a -new, boxed instance of the `PendingReview` struct, which is a new type we've -introduced that represents the state when a post is waiting for a review. The -`PendingReview` struct also implements the `request_review` method, but it -doesn't do any transformations. It returns itself since requesting a review on -a post that's already in the `PendingReview` state should stay in the -`PendingReview` state. +`Draft` 的方法 `request_review` 的实现返回一个新的,装箱的 `PendingReview` 结构体的实例,这是新引入的用来代表博文处于等待审核状态的类型。结构体 `PendingReview` 同样也实现了 `request_review` 方法,不过它不进行任何状态转换。它返回自身,因为请求审核已经处于 `PendingReview` 状态的博文应该保持 `PendingReview` 状态。 -Now we can start seeing the advantages of the state pattern: the -`request_review` method on `Post` is the same no matter what its `state` value -is. Each state is responsible for its own rules. +现在能够看出状态模式的优势了:`Post` 的 `request_review` 方法无论 `state` 是何值都是一样的。每个状态负责它自己的规则。 -We're going to leave the `content` method on `Post` as it is, returning an -empty string slice. We can now have a `Post` in the `PendingReview` state, not -just the `Draft` state, but we want the same behavior in the `PendingReview` -state. Listing 17-11 now works up until line 11! +我们将继续保持 `Post` 的 `content` 方法不变,返回一个空字符串 slice。现在可以拥有 `PendingReview` 状态而不仅仅是 `Draft` 状态的 `Post` 了,不过我们希望在 `PendingReview` 状态下其也有相同的行为。现在列表 17-11 中直到 11 行的代码是可以执行的! -### Approving a Post Changes the Behavior of `content` +### 批准博文并改变 `content` 的行为 -The `approve` method on `Post` will be similar to that of the `request_review` -method: it will set the `state` to the value that the current state says it -should have when that state is approved. We'll need to add the `approve` method -to the `State` trait, and we'll add a new struct that implements `State`, the -`Published` state. Listing 17-16 shows the new code: +`Post` 的 `approve` 方法将与 `request_review` 方法类似:它会将 `state` 设置为审核通过时应处于的状态。我们需要为 `State` trait 增加 `approve` 方法,并需新增实现了 `State` 的结构体, `Published` 状态。列表 17-16 展示了新增的代码: -Filename: src/lib.rs +文件名: src/lib.rs ```rust # pub struct Post { @@ -267,25 +241,13 @@ impl State for Published { } ``` -Listing 17-16: Implementing the `approve` method on -`Post` and the `State` trait +列表 17-16: 为 `Post` 和 `State` trait 实现 `approve` 方法 -Similarly to `request_review`, if we call the `approve` method on a `Draft`, it -will have no effect since it will return `self`. When we call `approve` on -`PendingReview`, it returns a new, boxed instance of the `Published` struct. -The `Published` struct implements the `State` trait, and for both the -`request_review` method and the `approve` method, it returns itself since the -post should stay in the `Published` state in those cases. +类似于 `request_review`,如果对 `Draft` 调用 `approve` 方法,并没有任何效果,因为它会返回 `self`。当对 `PendingReview` 调用 `approve` 时,它返回一个新的、装箱的 `Published` 结构体的实例。`Published` 结构体实现了 `State` trait,同时对于 `request_review` 和 `approve` 方法来说,它返回自身,因为在这两种情况博文应该保持 `Published` 状态。 -Now for updating the `content` method on `Post`: we want to return the value in -the post's `content` field if its state is `Published`, otherwise we want to -return an empty string slice. Because the goal is to keep all the rules like -this in the structs that implement `State`, we're going to call a `content` -method on the value in `state` and pass the post instance (that is, `self`) as -an argument. Then we'll return the value returned from the `content` method on -the `state` value as shown in Listing 17-17: +现在更新 `Post` 的 `content` 方法:我们希望当博文处于 `Published` 时返回 `content` 字段的值,否则返回空字符串 slice。因为目标是将所有像这样的规则保持在实现了 `State` 的结构体中,我们将调用 `state` 中的值的 `content` 方法并传递博文实例(也就是 `self`)作为参数。接着返回 `state` 值的 `content` 方法的返回值,如列表 17-17 所示: -Filename: src/lib.rs +文件名: src/lib.rs ```rust # trait State { @@ -305,24 +267,13 @@ impl Post { } ``` -Listing 17-17: Updating the `content` method on `Post` to -delegate to a `content` method on `State` +列表 17-17: 更新 `Post` 的 `content` 方法来委托调用 `State` 的`content` 方法 -We're calling the `as_ref` method on the `Option` because we want a reference -to the value inside the `Option`. We're then calling the `unwrap` method, which -we know will never panic because all the methods on `Post` ensure that the -`state` value will have a `Some` value in it when those methods are done. This -is one of the cases we talked about in Chapter 12 where we know that a `None` -value is never possible even though the compiler isn't able to understand that. +这里调用 `Option` 的 `as_ref`方法是因为需要 `Option` 中值的引用。接着调用 `unwrap` 方法,这里我们知道永远也不会 panic 因为 `Post` 的所有方法都确保在他们返回时 `state` 会有一个 `Some` 值。这就是一个第十二章讨论过的我们知道 `None` 是不可能的而编译器却不能理解的情况。 -The `content` method on the `State` trait is where the logic for what content -to return will be. We're going to add a default implementation for the -`content` method that returns an empty string slice. That lets us not need to -implement `content` on the `Draft` and `PendingReview` structs. The `Published` -struct will override the `content` method and will return the value in -`post.content`, as shown in Listing 17-18: +`State` trait 的 `content` 方法是博文返回什么内容的逻辑所在之处。我们将增加一个 `content` 方法的默认实现来返回一个空字符串 slice。这样就无需为 `Draft` 和 `PendingReview` 结构体实现 `content` 了。`Published` 结构体会覆盖 `content` 方法并会返回 `post.content` 的值,如列表 17-18 所示: -Filename: src/lib.rs +文件名: src/lib.rs ```rust # pub struct Post { @@ -346,87 +297,39 @@ impl State for Published { } ``` -Listing 17-18: Adding the `content` method to the `State` -trait +列表 17-18: 为 `State` trait 增加 `content` 方法 -Note that we need lifetime annotations on this method, like we discussed in -Chapter 10. We're taking a reference to a `post` as an argument, and we're -returning a reference to a part of that `post`, so the lifetime of the returned -reference is related to the lifetime of the `post` argument. +注意这个方法需要生命周期注解,如第十章所讨论的。这里获取 `post` 的引用作为参数,并返回 `post` 一部分的引用,所以返回的引用的生命周期与 `post` 参数相关。 -### Tradeoffs of the State Pattern +### 状态模式的权衡取舍 -We've shown that Rust is capable of implementing the object-oriented state -pattern in order to encapsulate the different kinds of behavior that a post -should have that depends on the state that the post is in. The methods on -`Post` don't know anything about the different kinds of behavior. The way this -code is organized, we have one place to look in order to find out all the -different ways that a published post behaves: the implementation of the `State` -trait on the `Published` struct. +我们展示了 Rust 是能够实现面向对象的状态模式的,以便能根据博文所处的状态来封装不同类型的行为。`Post` 的方法并不知道这些不同类型的行为。这种组织代码的方式,为了找到所有已发布的博文不同行为只需查看一处代码:`Published` 的 `State` trait 的实现。 -An alternative implementation that didn't use the state pattern might have -`match` statements in the methods on `Post` or even in the code that uses -`Post` (`main` in our case) that checks what the state of the post is and -changes behavior in those places instead. That would mean we'd have a lot of -places to look in order to understand all the implications of a post being in -the published state! This would get worse the more states we added: each of -those `match` statements would need another arm. With the state pattern, the -`Post` methods and the places we use `Post` don't need `match` statements and -adding a new state only involves adding a new `struct` and implementing the -trait methods on that one struct. +一个不使用状态模式的替代实现可能会在 `Post` 的方法中,甚至于在使用 `Post` 的代码中(在这里是 `main` 中)用到 `match` 语句,来检查博文状态并在这里改变其行为。这可能意味着需要查看很多位置来理解处于发布状态的博文的所有逻辑!这在增加更多状态时会变得更糟:每一个 `match` 语句都会需要另一个分支。对于状态模式来说,`Post` 的方法和使用 `Post` 的位置无需`match` 语句,同时增加新状态只涉及到增加一个新 `struct` 和为其实现 trait 的方法。 -This implementation is easy to extend to add more functionality. Here are some -changes you can try making to the code in this section to see for yourself what -it's like to maintain code using this pattern over time: +这个实现易于增加更多功能。这里是一些你可以尝试对本部分代码做出的修改,来亲自体会一下使用状态模式随着时间的推移维护代码是什么感觉: -- Only allow adding text content when a post is in the `Draft` state -- Add a `reject` method that changes the post's state from `PendingReview` back - to `Draft` -- Require two calls to `approve` before changing the state to `Published` +- 只允许博文处于 `Draft` 状态时增加文本内容 +- 增加 `reject` 方法将博文的状态从 `PendingReview` 变回 `Draft` +- 在将状态变为 `Published` 之前需要两次 `approve` 调用 -A downside of the state pattern is that since the states implement the -transitions between the states, some of the states are coupled to each other. -If we add another state between `PendingReview` and `Published`, such as -`Scheduled`, we would have to change the code in `PendingReview` to transition -to `Scheduled` instead. It would be nicer if `PendingReview` wouldn't need to -change because of the addition of a new state, but that would mean switching to -another design pattern. +状态模式的一个缺点是因为状态实现了状态之间的转换,一些状态会相互联系。如果在 `PendingReview` 和 `Published` 之间增加另一个状态,比如 `Scheduled`,则不得不修改 `PendingReview` 中的代码来转移到 `Scheduled`。如果 `PendingReview` 无需因为新增的状态而改变就更好了,不过这意味着切换到另一个设计模式。 -There are a few bits of duplicated logic that are a downside of this -implementation in Rust. It would be nice if we could make default -implementations for the `request_review` and `approve` methods on the `State` -trait that return `self`, but this would violate object safety since the trait -doesn't know what the concrete `self` will be exactly. We want to be able to -use `State` as a trait object, so we need its methods to be object safe. +这个 Rust 中的实现的缺点在于存在一些重复的逻辑。如果能够为 `State` trait 中返回 `self` 的 `request_review` 和 `approve` 方法增加默认实现就好了,不过这会违反对象安全性,因为 trait 不知道 `self` 具体是什么。我们希望能够将 `State` 作为一个 trait 对象,所以需要这个方法是对象安全的。 -The other duplication that would be nice to get rid of is the similar -implementations of the `request_review` and `approve` methods on `Post`. They -both delegate to the implementation of the same method on the value in the -`Option` in the `state` field, and set the new value of the `state` field to -the result. If we had a lot of methods on `Post` that followed this pattern, we -might consider defining a macro to eliminate the repetition (see Appendix E on -macros). +另一个最好能去除的重复是 `Post` 中 `request_review` 和 `approve` 这两个类似的实现。他们都委托调用了 `state` 字段中 `Option` 值的同一方法,并在结果中为 `state` 字段设置了新值。如果 `Post` 中的很多方法都遵循这个模式,我们可能会考虑定义一个宏来消除重复(查看附录 E 以了解宏)。 -A downside of implementing this object-oriented pattern exactly as it's defined -for object-oriented languages is that we're not taking advantage of Rust's -strengths as much as we could be. Let's take a look at some changes we can make -to this code that can make invalid states and transitions into compile time -errors. +这个完全按照面向对象语言的定义实现的面向对象模式的缺点在于没有尽可能的利用 Rust 的优势。让我们看看一些代码中可以做出的修改,来将无效的状态和状态转移变为编译时错误。 -#### Encoding States and Behavior as Types +#### 将状态和行为编码为类型 -We're going to show how to rethink the state pattern a bit in order to get a -different set of tradeoffs. Rather than encapsulating the states and -transitions completely so that outside code has no knowledge of them, we're -going to encode the states into different types. When the states are types, -Rust's type checking will make any attempt to use a draft post where we should -only use published posts into a compiler error. +我们将展示如何稍微反思状态模式来进行一系列不同的权衡取舍。不同于完全封装状态和状态转移使得外部代码对其毫不知情,我们将将状态编码进不同的类型。当状态是类型时,Rust 的类型检查就会使任何在只能使用发布的博文的地方使用草案博文的尝试变为编译时错误。 -Let's consider the first part of `main` from Listing 17-11: +让我们考虑一下列表 17-11 中 `main` 的第一部分: -Filename: src/main.rs +文件名: src/main.rs -```rust,ignore +```rust fn main() { let mut post = Post::new(); @@ -435,17 +338,9 @@ fn main() { } ``` -We still want to create a new post in the draft state using `Post::new`, and we -still want to be able to add text to the post's content. But instead of having -a `content` method on a draft post that returns an empty string, we're going to -make it so that draft posts don't have the `content` method at all. That way, -if we try to get a draft post's content, we'll get a compiler error that the -method doesn't exist. This will make it impossible for us to accidentally -display draft post content in production, since that code won't even compile. -Listing 17-19 shows the definition of a `Post` struct, a `DraftPost` struct, -and methods on each: +我们仍然希望使用 `Post::new` 创建一个新的草案博文,并仍然希望能够增加博文的内容。不过不同于存在一个草案博文时返回空字符串的 `content` 方法,我们将使草案博文完全没有 `content` 方法。这样如果尝试获取草案博文的内容,将会得到一个方法不存在的编译错误。这使得我们不可能在生产环境意外显示出草案博文的内容,因为这样的代码甚至就不能编译。列表 17-19 展示了 `Post` 结构体、`DraftPost` 结构体以及各自的方法的定义: -Filename: src/lib.rs +文件名: src/lib.rs ```rust pub struct Post { @@ -475,34 +370,17 @@ impl DraftPost { } ``` -Listing 17-19: A `Post` with a `content` method and a -`DraftPost` without a `content` method +列表 17-19: 带有 `content` 方法的 `Post` 和没有 `content` 方法的 `DraftPost` -Both the `Post` and `DraftPost` structs have a private `content` field that stores the -blog post text. The structs no longer have the `state` field since we're moving -the encoding of the state to the types of the structs. `Post` will represent a -published post, and it has a `content` method that returns the `content`. +`Post` 和 `DraftPost` 结构体都有一个私有的 `content` 字段来储存博文的文本。这些结构体不再有 `state` 字段因为我们将类型编码为结构体的类型。`Post` 将代表发布的博文,它有一个返回 `content` 的 `content` 方法。 -We still have a `Post::new` function, but instead of returning an instance of -`Post`, it returns an instance of `DraftPost`. It's not possible to create an -instance of `Post` right now since `content` is private and there aren't any -functions that return `Post`. `DraftPost` has an `add_text` method defined on -it so that we can add text to `content` as before, but note that `DraftPost` -does not have a `content` method defined! So we've enforced that all posts -start as draft posts, and draft posts don't have their content available for -display. Any attempt to get around these constraints will be a compiler error. +仍然有一个 `Post::new` 函数,不过不同于返回 `Post` 实例,它返回 `DraftPost` 的实例。现在不可能创建一个 `Post` 实例,因为 `content` 是私有的同时没有任何函数返回 `Post`。`DraftPost` 上定义了一个 `add_text` 方法,这样就可以像之前那样向 `content` 增加文本,不过注意 `DraftPost` 并没有定义 `content` 方法!所以所有博文都强制从草案开始,同时草案博文没有任何可供展示的内容。任何绕过这些限制的尝试都会产生编译错误。 -#### Implementing Transitions as Transformations into Different Types +#### 实现状态转移为不同类型的转移 -So how do we get a published post then? The rule we want to enforce is that a -draft post has to be reviewed and approved before it can be published. A post -in the pending review state should still not display any content. Let's -implement these constraints by adding another struct, `PendingReviewPost`, -defining the `request_review` method on `DraftPost` to return a -`PendingReviewPost`, and defining an `approve` method on `PendingReviewPost` to -return a `Post` as shown in Listing 17-20: +那么如何得到发布的博文呢?我们希望强制的规则是草案博文在可以发布之前必须被审核通过。等待审核状态的博文应该仍然不会显示任何内容。让我们通过增加另一个结构体 `PendingReviewPost` 来实现这个限制,在 `DraftPost` 上定义 `request_review` 方法来返回 `PendingReviewPost`,并在 `PendingReviewPost` 上定义 `approve` 方法来返回 `Post`,如列表 17-20 所示: -Filename: src/lib.rs +文件名: src/lib.rs ```rust # pub struct Post { @@ -536,30 +414,11 @@ impl PendingReviewPost { } ``` -Listing 17-20: A `PendingReviewPost` that gets created by -calling `request_review` on `DraftPost`, and an `approve` method that turns a -`PendingReviewPost` into a published `Post` +列表 17-20: `PendingReviewPost` 通过调用 `DraftPost` 的 `request_review` 创建,`approve` 方法将 `PendingReviewPost` 变为发布的 `Post` -The `request_review` and `approve` methods take ownership of `self`, thus -consuming the `DraftPost` and `PendingReviewPost` instances and transforming -them into a `PendingReviewPost` and a published `Post`, respectively. This way, -we won't have any `DraftPost` instances lingering around after we've called -`request_review` on them, and so forth. `PendingReviewPost` doesn't have a -`content` method defined on it, so attempting to read its content is a compiler -error like it is with `DraftPost`. Because the only way to get a published -`Post` instance that does have a `content` method defined is to call the -`approve` method on a `PendingReviewPost`, and the only way to get a -`PendingReviewPost` is to call the `request_review` method on a `DraftPost`, -we've now encoded the blog post workflow into the type system. +`request_review` 和 `approve` 方法获取 `self` 的所有权,因此会消费 `DraftPost` 和 `PendingReviewPost` 实例,并分别转换为 `PendingReviewPost` 和 发布的 `Post`。这样在调用 `request_review` 之后就不会遗留任何 `DraftPost` 实例,后者同理。`PendingReviewPost` 并没有定义 `content` 方法,所以类似 `DraftPost` 尝试读取它的内容是一个编译错误。因为唯一得到定义了 `content` 方法的 `Post` 实例的途径是调用 `PendingReviewPost` 的 `approve` 方法,而得到 `PendingReviewPost` 的唯一办法是调用 `DraftPost` 的 `request_review` 方法,现在我们就将发博文的工作流编码进了类型系统。 -This does mean we have to make some small changes to `main`. Because -`request_review` and `approve` return new instances rather than modifying the -struct they're called on, we need to add more `let post = ` shadowing -assignments to save the returned instances. We also can't have the assertions -about the draft and pending review post's contents being empty string anymore, -nor do we need them: we can't compile code that tries to use the content of -posts in those states any longer. The updated code in `main` is shown in -Listing 17-21: +这也意味着不得不对 `main`做出一些小的修改。因为 `request_review` 和 `approve` 返回新实例而不是修改被调用的结构体,我们需要增加更多的 `let post = ` 覆盖赋值来保存返回的实例。也不能再断言草案和等待审核的博文的内容为空字符串了,我们也不再需要他们:不能编译尝试使用这些状态下博文内容的代码。更新后的 `main` 的代码如列表 18-21 所示: Filename: src/main.rs @@ -580,41 +439,16 @@ fn main() { } ``` -Listing 17-21: Modifications to `main` to use the new -implementation of the blog post workflow +列表 17-21: `main` 中使用新的博文工作流实现的修改 -Having to change `main` to reassign `post` is what makes this implementation -not quite following the object-oriented state pattern anymore: the -transformations between the states are no longer encapsulated entirely within -the `Post` implementation. However, we've gained the property of having invalid -states be impossible because of the type system and type checking that happens -at compile time! This ensures that certain bugs, such as displaying the content -of an unpublished post, will be discovered before they make it to production. +不得不修改 `main` 来重新赋值 `post` 使得这个实现不再完全遵守面向对象的状态模式:状态间的转换不再完全封装在 `Post` 实现中。然而,得益于类型系统和编译时类型检查我们得到了不可能拥有无效状态的属性!这确保了特定的 bug,比如显示未发布博文的内容,将在部署到生产环境之前被发现。 -Try the tasks suggested that add additional requirements that we mentioned at -the start of this section to see how working with this version of the code -feels. +尝试在这一部分开始所建议的增加额外需求的任务来体会使用这个版本的代码是何感觉。 -Even though Rust is capable of implementing object-oriented design patterns, -there are other patterns like encoding state into the type system that are -available in Rust. These patterns have different tradeoffs than the -object-oriented patterns do. While you may be very familiar with -object-oriented patterns, rethinking the problem in order to take advantage of -Rust's features can give benefits like preventing some bugs at compile-time. -Object-oriented patterns won't always be the best solution in Rust, since Rust -has features like ownership that object-oriented languages don't have. +即便 Rust 能够实现面向对象设计模式,也有其他像将状态编码进类型这样的模式存在。这些模式有着不同于面向对象模式的权衡取舍。虽然你可能非常熟悉面向对象模式,重新思考这些问题来利用 Rust 提供的像在编译时避免一些 bug 这样有益功能。在 Rust 中面向对象模式并不总是最好的解决方案,因为 Rust 拥有像所有权这样的面向对象语言所没有的功能。 -## Summary +## 总结 -No matter whether you think Rust is an object-oriented language or not after -reading this chapter, you've now seen that trait objects are a way to get some -object-oriented features in Rust. Dynamic dispatch can give your code some -flexibility in exchange for a bit of runtime performance. This flexibility can -be used to implement object-oriented patterns that can help with the -maintainability of your code. Rust also has different features, like ownership, -than object-oriented languages. An object-oriented pattern won't always be the -best way to take advantage of Rust's strengths. +阅读本章后,不管你是否认为 Rust 是一个面向对象语言,现在你都见识了 trait 对象是一个 Rust 中获取部分面向对象功能的方法。动态分发可以通过牺牲一些运行时性能来为你的代码提供一些灵活性。这些灵活性可以用来实现有助于代码可维护性的面向对象模式。Rust 也有像所有权这样不同于面向对象语言的功能。面向对象模式并不总是利用 Rust 实力的最好方式。 -Next, let's look at another feature of Rust that enables lots of flexibility: -patterns. We've looked at them briefly throughout the book, but haven't seen -everything they're capable of yet. Let's go! +接下来,让我们看看另一个提供了很多灵活性的 Rust 功能:模式。贯穿本书我们都曾简单的见过他们,但并没有见识过他们的全部本领。让我们开始吧! From 057691cbc71014aaf1c53d61fd5e271495176a39 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 19 Jun 2017 23:05:46 +0800 Subject: [PATCH 13/59] Create ch18-02-refutability.md --- src/ch18-02-refutability.md | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/ch18-02-refutability.md diff --git a/src/ch18-02-refutability.md b/src/ch18-02-refutability.md new file mode 100644 index 0000000..f71b776 --- /dev/null +++ b/src/ch18-02-refutability.md @@ -0,0 +1,100 @@ +## Refutability: Whether a Pattern Might Fail to Match + +Patterns come in two forms: refutable and irrefutable. Patterns which cannot +fail to match for any possible value are said to be *irrefutable*, and patterns +which can fail to match for some possible value are said to be *refutable*. +`let` statements, function parameters, and `for` loops are restricted to only +accept irrefutable patterns, since there's nothing correct the program could do +if the pattern fails to match. `if let`, and `while let` expressions are +restricted to only accept refutable patterns, since they're made to handle +possible failure and we wouldn't need their functionality if the pattern could +never fail. + +In general, you shouldn't have to worry about the distinction between refutable +and irrefutable patterns; just be familiar with the concept of refutability +when you see it mentioned in an error message. When you get an error message +involving refutability, you'll need to change either the pattern or the +construct you're using the pattern with, depending on your intentions for the +behavior of the code. + +Let's look at some examples. Earlier in this chapter, we had `let x = 5;`. `x` +is indeed an irrefutable pattern we're allowed to use: since it matches +anything, it can't fail to match. In contrast, consider trying to match one +variant of an enum with `let`, such as matching only a `Some` value from the +`Option` enum as shown in Listing 18-7: + +```rust,ignore +let Some(x) = some_option_value; +``` + +Listing 18-7: Attempting to use a refutable pattern with +`let` + +If `some_option_value` was a `None` value, `some_option_value` would not match +the pattern `Some(x)`. The pattern `Some(x)` is refutable since there exists a +case in which it would fail to match a value. There's nothing valid that our +code could do with this `let` statement if `some_option_value` was the `None` +value. Therefore, Rust will complain at compile time that we've tried to use a +refutable pattern where an irrefutable pattern is required: + +```text +error[E0005]: refutable pattern in local binding: `None` not covered + --> :3:5 + | +3 | let Some(x) = some_option_value; + | ^^^^^^^ pattern `None` not covered +``` + +We didn't cover (and couldn't cover!) every valid value with the pattern +`Some(x)`, so Rust will rightfully complain. + +If we have a refutable pattern, instead of using `let`, we can use `if let`. +That way, if the pattern doesn't match, the code inside the curly braces won't +execute. That code will only make sense and run if the value matches the +pattern. Listing 18-8 shows how to fix the code in Listing 18-7 with `Some(x)` +matching `some_option_value`. Using the refutable pattern `Some(x)` is allowed, +since this example uses `if let`: + +```rust +# let some_option_value: Option = None; +if let Some(x) = some_option_value { + println!("{}", x); +} +``` + +Listing 18-8: Using `if let` and a block with refutable +patterns instead of `let` + +Consequently, if we give `if let` an irrefutable pattern that will always match, +such as `x` as shown in Listing 18-9: + +```rust,ignore +if let x = 5 { + println!("{}", x); +}; +``` + +Listing 18-9: Attempting to use an irrefutable pattern +with `if let` + +Rust will complain that it doesn't make sense to use `if let` with an +irrefutable pattern: + +```text +error[E0162]: irrefutable if-let pattern + --> :2:8 + | +2 | if let x = 5 { + | ^ irrefutable pattern +``` + +Generally, match arms use refutable patterns, except for the last arm that +might match any remaining values with an irrefutable pattern. A `match` with +only one arm whose pattern is irrefutable is allowed, but it's not particularly +useful and could be replaced with a simpler `let` statement. Both the expressions +associated with a `let` statement and a single arm irrefutable match will +unconditionally be run, so the end result is the same if their expressions are. + +Now that we've discussed all the places that patterns can be used and the +difference between refutable and irrefutable patterns, let's go over all the +syntax we can use to create patterns. From 433a15c7c1eb49ebd77f0c29ba792546c1c1a732 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 19 Jun 2017 23:18:14 +0800 Subject: [PATCH 14/59] Update ch18-02-refutability.md --- src/ch18-02-refutability.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ch18-02-refutability.md b/src/ch18-02-refutability.md index f71b776..efd9a53 100644 --- a/src/ch18-02-refutability.md +++ b/src/ch18-02-refutability.md @@ -1,8 +1,6 @@ -## Refutability: Whether a Pattern Might Fail to Match +## Refutability(可反驳性): 模式是否会匹配失效 -Patterns come in two forms: refutable and irrefutable. Patterns which cannot -fail to match for any possible value are said to be *irrefutable*, and patterns -which can fail to match for some possible value are said to be *refutable*. +模式有两种形式: refutable(可反驳)和irrefutable(不可反驳). 对任意可能的值进行匹配都不会失效的模式被称为是*irrefutable*(不可反驳)的, 而对某些可能的值进行匹配会失效的模式被称为是*refutable*(可反驳)的. `let` statements, function parameters, and `for` loops are restricted to only accept irrefutable patterns, since there's nothing correct the program could do if the pattern fails to match. `if let`, and `while let` expressions are From a5adb9dda21c10e54036d6cd20a68dec2d58a4ae Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Tue, 20 Jun 2017 23:37:29 +0800 Subject: [PATCH 15/59] Update ch18-02-refutability.md --- src/ch18-02-refutability.md | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/ch18-02-refutability.md b/src/ch18-02-refutability.md index efd9a53..f49aa62 100644 --- a/src/ch18-02-refutability.md +++ b/src/ch18-02-refutability.md @@ -1,21 +1,11 @@ ## Refutability(可反驳性): 模式是否会匹配失效 -模式有两种形式: refutable(可反驳)和irrefutable(不可反驳). 对任意可能的值进行匹配都不会失效的模式被称为是*irrefutable*(不可反驳)的, 而对某些可能的值进行匹配会失效的模式被称为是*refutable*(可反驳)的. -`let` statements, function parameters, and `for` loops are restricted to only -accept irrefutable patterns, since there's nothing correct the program could do -if the pattern fails to match. `if let`, and `while let` expressions are -restricted to only accept refutable patterns, since they're made to handle -possible failure and we wouldn't need their functionality if the pattern could -never fail. +匹配模式有两种形式: refutable(可反驳)和irrefutable(不可反驳). 对任意可能的值进行匹配都不会失效的模式被称为是*irrefutable*(不可反驳)的, 而对某些可能的值进行匹配会失效的模式被称为是*refutable*(可反驳)的. +`let`语句、 函数参数和`for`循环被约束为只接受*irrefutable*模式, 因为如果模式匹配失效程序就不会正确运行. `if let`和`while let`表达式被约束为只接受*refutable*模式, 因为它们需要处理可能存在的匹配失效的情况, 并且如果模式匹配永不失效, 那它们就派不上用场了. -In general, you shouldn't have to worry about the distinction between refutable -and irrefutable patterns; just be familiar with the concept of refutability -when you see it mentioned in an error message. When you get an error message -involving refutability, you'll need to change either the pattern or the -construct you're using the pattern with, depending on your intentions for the -behavior of the code. +通常, 你不用关心*refutable*和*irrefutable*模式的区别, 当你看见它出现在了错误消息中时, 你只要了解*可反驳性*(refutability)的概念即可. 如果你得到一个涉及到可反驳性概念的错误消息, 根据你的代码行为的意图, 你只需改变匹配模式或者是改变你构造模式的方法即可. -Let's look at some examples. Earlier in this chapter, we had `let x = 5;`. `x` +让我们来看几个例子. Earlier in this chapter, we had `let x = 5;`. `x` is indeed an irrefutable pattern we're allowed to use: since it matches anything, it can't fail to match. In contrast, consider trying to match one variant of an enum with `let`, such as matching only a `Some` value from the From 238da177a583e7015493acb3abc69bc4f5694577 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Tue, 20 Jun 2017 23:52:13 +0800 Subject: [PATCH 16/59] Update ch18-02-refutability.md --- src/ch18-02-refutability.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/ch18-02-refutability.md b/src/ch18-02-refutability.md index f49aa62..607c6de 100644 --- a/src/ch18-02-refutability.md +++ b/src/ch18-02-refutability.md @@ -5,18 +5,13 @@ 通常, 你不用关心*refutable*和*irrefutable*模式的区别, 当你看见它出现在了错误消息中时, 你只要了解*可反驳性*(refutability)的概念即可. 如果你得到一个涉及到可反驳性概念的错误消息, 根据你的代码行为的意图, 你只需改变匹配模式或者是改变你构造模式的方法即可. -让我们来看几个例子. Earlier in this chapter, we had `let x = 5;`. `x` -is indeed an irrefutable pattern we're allowed to use: since it matches -anything, it can't fail to match. In contrast, consider trying to match one -variant of an enum with `let`, such as matching only a `Some` value from the -`Option` enum as shown in Listing 18-7: +让我们来看几个例子. 在本章的前面部分, 我们提到`let x = 5;`. 这里`x`就是一个我们被允许使用*irrefutable*的模式: 因为它不可能匹配失效. 相反, 如果用`let`来匹配一个枚举的变体, 比如像**例18-7**中列出的那样从`Option`枚举中只匹配`Some`这个值: ```rust,ignore let Some(x) = some_option_value; ``` -Listing 18-7: Attempting to use a refutable pattern with -`let` +例18-7: 试试用一个有`let`的*refutable*模式 If `some_option_value` was a `None` value, `some_option_value` would not match the pattern `Some(x)`. The pattern `Some(x)` is refutable since there exists a From b86eb688aa2f2e5c058a32ada00ae2b96044335c Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 21 Jun 2017 12:10:38 +0800 Subject: [PATCH 17/59] Update ch18-02-refutability.md --- src/ch18-02-refutability.md | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/ch18-02-refutability.md b/src/ch18-02-refutability.md index 607c6de..8e9e92b 100644 --- a/src/ch18-02-refutability.md +++ b/src/ch18-02-refutability.md @@ -13,12 +13,7 @@ let Some(x) = some_option_value; 例18-7: 试试用一个有`let`的*refutable*模式 -If `some_option_value` was a `None` value, `some_option_value` would not match -the pattern `Some(x)`. The pattern `Some(x)` is refutable since there exists a -case in which it would fail to match a value. There's nothing valid that our -code could do with this `let` statement if `some_option_value` was the `None` -value. Therefore, Rust will complain at compile time that we've tried to use a -refutable pattern where an irrefutable pattern is required: +如果`some_option_value`的值是`None`, `some_option_value`将不会匹配模式`Some(x)`. 模式`Some(x)`是可反驳的(refutable), 因为存在一个使它匹配失效的值. 如果`some_option_value`的值是`None`, 那么`let`语句就不会产生任何效果. 因此Rust会在编译时会报*期望irrefutable模式但是却得到了一个refutable模式*的错误: ```text error[E0005]: refutable pattern in local binding: `None` not covered @@ -28,13 +23,9 @@ error[E0005]: refutable pattern in local binding: `None` not covered | ^^^^^^^ pattern `None` not covered ``` -We didn't cover (and couldn't cover!) every valid value with the pattern -`Some(x)`, so Rust will rightfully complain. +因为我们没有(也不能)覆盖到模式`Some(x)`的每一个可能的值, 所以Rust会报错. -If we have a refutable pattern, instead of using `let`, we can use `if let`. -That way, if the pattern doesn't match, the code inside the curly braces won't -execute. That code will only make sense and run if the value matches the -pattern. Listing 18-8 shows how to fix the code in Listing 18-7 with `Some(x)` +如果我们采用*refutable*模式, 使用`if let`而不是`let`. 这样当模式不匹配时, 在花括号中的代码将不执行, 这段代码只有在值匹配模式的时候才会执行, 也只在此时有意义. Listing 18-8 shows how to fix the code in Listing 18-7 with `Some(x)` matching `some_option_value`. Using the refutable pattern `Some(x)` is allowed, since this example uses `if let`: From 5b11d7c57468bd9a7a9c74101140de39b2fc0989 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 21 Jun 2017 19:53:49 +0800 Subject: [PATCH 18/59] Update ch18-02-refutability.md --- src/ch18-02-refutability.md | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/ch18-02-refutability.md b/src/ch18-02-refutability.md index 8e9e92b..9584fbb 100644 --- a/src/ch18-02-refutability.md +++ b/src/ch18-02-refutability.md @@ -25,9 +25,7 @@ error[E0005]: refutable pattern in local binding: `None` not covered 因为我们没有(也不能)覆盖到模式`Some(x)`的每一个可能的值, 所以Rust会报错. -如果我们采用*refutable*模式, 使用`if let`而不是`let`. 这样当模式不匹配时, 在花括号中的代码将不执行, 这段代码只有在值匹配模式的时候才会执行, 也只在此时有意义. Listing 18-8 shows how to fix the code in Listing 18-7 with `Some(x)` -matching `some_option_value`. Using the refutable pattern `Some(x)` is allowed, -since this example uses `if let`: +如果我们采用*refutable*模式, 使用`if let`而不是`let`. 这样当模式不匹配时, 在花括号中的代码将不执行, 这段代码只有在值匹配模式的时候才会执行, 也只在此时才有意义. 例18-8显示了如何修正在例18-7中用`Some(x)`来匹配`some_option_value`的代码. 因为这个例子使用了`if let`, 因此使用*refutable*模式的`Some(x)`就没问题了: ```rust # let some_option_value: Option = None; @@ -36,11 +34,9 @@ if let Some(x) = some_option_value { } ``` -Listing 18-8: Using `if let` and a block with refutable -patterns instead of `let` +例18-8: 使用`if let`和一个有*refutable*模式的代码块来代替`let` -Consequently, if we give `if let` an irrefutable pattern that will always match, -such as `x` as shown in Listing 18-9: +此外, 如果我们给`if let`一个绝对会匹配的*irrefutable*模式, 比如在例18-9中显示的`x`: ```rust,ignore if let x = 5 { @@ -48,11 +44,9 @@ if let x = 5 { }; ``` -Listing 18-9: Attempting to use an irrefutable pattern -with `if let` +例18-9: 尝试把一个*irrefutable*模式用到`if let`上 -Rust will complain that it doesn't make sense to use `if let` with an -irrefutable pattern: +Rust将会抱怨把`if let`和一个*irrefutable*模式一起使用没有意义: ```text error[E0162]: irrefutable if-let pattern @@ -62,10 +56,7 @@ error[E0162]: irrefutable if-let pattern | ^ irrefutable pattern ``` -Generally, match arms use refutable patterns, except for the last arm that -might match any remaining values with an irrefutable pattern. A `match` with -only one arm whose pattern is irrefutable is allowed, but it's not particularly -useful and could be replaced with a simpler `let` statement. Both the expressions +一般来说, 多数匹配使用*refutable*模式, 除非是那种可以匹配任意值的情况使用*irrefutable*模式. `match`操作符中如果只有一个*irrefutable*模式分支也没有什么问题, 但这就没什么特别的用处, 此时可以用一个更简单的`let`语句来替换. 不管是把表达式关联到`let`语句亦或是 Both the expressions associated with a `let` statement and a single arm irrefutable match will unconditionally be run, so the end result is the same if their expressions are. From 39b5b99c3246b1b665d0e0e02664c0e0223b813c Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 21 Jun 2017 22:48:36 +0800 Subject: [PATCH 19/59] Update ch18-02-refutability.md --- src/ch18-02-refutability.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ch18-02-refutability.md b/src/ch18-02-refutability.md index 9584fbb..95e1718 100644 --- a/src/ch18-02-refutability.md +++ b/src/ch18-02-refutability.md @@ -56,10 +56,6 @@ error[E0162]: irrefutable if-let pattern | ^ irrefutable pattern ``` -一般来说, 多数匹配使用*refutable*模式, 除非是那种可以匹配任意值的情况使用*irrefutable*模式. `match`操作符中如果只有一个*irrefutable*模式分支也没有什么问题, 但这就没什么特别的用处, 此时可以用一个更简单的`let`语句来替换. 不管是把表达式关联到`let`语句亦或是 Both the expressions -associated with a `let` statement and a single arm irrefutable match will -unconditionally be run, so the end result is the same if their expressions are. +一般来说, 多数匹配使用*refutable*模式, 除非是那种可以匹配任意值的情况使用*irrefutable*模式. `match`操作符中如果只有一个*irrefutable*模式分支也没有什么问题, 但这就没什么特别的用处, 此时可以用一个更简单的`let`语句来替换. 不管是把表达式关联到`let`语句亦或是关联到只有一个*irrefutable*模式分支的`match`操作, 代码都肯定会运行, 如果它们的表达式一样的话最终的结果也相同. -Now that we've discussed all the places that patterns can be used and the -difference between refutable and irrefutable patterns, let's go over all the -syntax we can use to create patterns. +目前我们已经讨论了所有可以使用模式的地方, 也介绍了*refutable*模式和*irrefutable*模式的不同, 下面让我们一起去把可以用来创建模式的语法过目一遍吧. From f413f3ac148ed4543abf0d5522ae9c01ae3f6cd2 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 21 Jun 2017 22:49:45 +0800 Subject: [PATCH 20/59] Update ch18-02-refutability.md Translation about refutability. --- src/ch18-02-refutability.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ch18-02-refutability.md b/src/ch18-02-refutability.md index 9584fbb..95e1718 100644 --- a/src/ch18-02-refutability.md +++ b/src/ch18-02-refutability.md @@ -56,10 +56,6 @@ error[E0162]: irrefutable if-let pattern | ^ irrefutable pattern ``` -一般来说, 多数匹配使用*refutable*模式, 除非是那种可以匹配任意值的情况使用*irrefutable*模式. `match`操作符中如果只有一个*irrefutable*模式分支也没有什么问题, 但这就没什么特别的用处, 此时可以用一个更简单的`let`语句来替换. 不管是把表达式关联到`let`语句亦或是 Both the expressions -associated with a `let` statement and a single arm irrefutable match will -unconditionally be run, so the end result is the same if their expressions are. +一般来说, 多数匹配使用*refutable*模式, 除非是那种可以匹配任意值的情况使用*irrefutable*模式. `match`操作符中如果只有一个*irrefutable*模式分支也没有什么问题, 但这就没什么特别的用处, 此时可以用一个更简单的`let`语句来替换. 不管是把表达式关联到`let`语句亦或是关联到只有一个*irrefutable*模式分支的`match`操作, 代码都肯定会运行, 如果它们的表达式一样的话最终的结果也相同. -Now that we've discussed all the places that patterns can be used and the -difference between refutable and irrefutable patterns, let's go over all the -syntax we can use to create patterns. +目前我们已经讨论了所有可以使用模式的地方, 也介绍了*refutable*模式和*irrefutable*模式的不同, 下面让我们一起去把可以用来创建模式的语法过目一遍吧. From c433926ea36b766a17c24c347461d738f493f145 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 26 Jun 2017 11:08:02 +0800 Subject: [PATCH 21/59] Pattern syntax. --- ch18-03-pattern-syntax.md | 697 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 697 insertions(+) create mode 100644 ch18-03-pattern-syntax.md diff --git a/ch18-03-pattern-syntax.md b/ch18-03-pattern-syntax.md new file mode 100644 index 0000000..795e221 --- /dev/null +++ b/ch18-03-pattern-syntax.md @@ -0,0 +1,697 @@ +## All the Pattern Syntax + +We've seen some examples of different kinds of patterns throughout the book. +This section lists all the syntax valid in patterns and why you might want to +use each of them. + +### Literals + +As we saw in Chapter 6, you can match against literals directly: + +```rust +let x = 1; + +match x { + 1 => println!("one"), + 2 => println!("two"), + 3 => println!("three"), + _ => println!("anything"), +} +``` + +This prints `one` since the value in `x` is 1. + +### Named Variables + +Named variables are irrefutable patterns that match any value. + +As with all variables, variables declared as part of a pattern will shadow +variables with the same name outside of the `match` construct since a `match` +starts a new scope. In Listing 18-10, we declare a variable named `x` with the +value `Some(5)` and a variable `y` with the value `10`. Then we have a `match` +expression on the value `x`. Take a look at the patterns in the match arms and +the `println!` at the end, and make a guess about what will be printed before +running this code or reading further: + +Filename: src/main.rs + +```rust +fn main() { + let x = Some(5); + let y = 10; + + match x { + Some(50) => println!("Got 50"), + Some(y) => println!("Matched, y = {:?}", y), + _ => println!("Default case, x = {:?}", x), + } + + println!("at the end: x = {:?}, y = {:?}", x, y); +} +``` + +Listing 18-10: A `match` statement with an arm that +introduces a shadowed variable `y` + + + +Let's walk through what happens when the `match` statement runs. The first +match arm has the pattern `Some(50)`, and the value in `x` (`Some(5)`) does not +match `Some(50)`, so we continue. In the second match arm, the pattern +`Some(y)` introduces a new variable name `y` that will match any value inside a +`Some` value. Because we're in a new scope inside the `match` expression, this +is a new variable, not the `y` we declared at the beginning that has the +value 10. The new `y` binding will match any value inside a `Some`, which is +what we have in `x`, so we execute the expression for that arm and print +`Matched, y = 5` since this `y` binds to the inner value of the `Some` in `x`, +which is 5. + +If `x` had been a `None` value instead of `Some(5)`, we would have matched the +underscore since the other two arms' patterns would not have matched. In the +expression for that match arm, since we did not introduce an `x` variable in +the pattern of the arm, this `x` is still the outer `x` that has not been +shadowed. In this hypothetical case, the `match` would print `Default case, x = +None`. + +Once the `match` expression is over, its scope ends, and so does the scope of +the inner `y`. The last `println!` produces `at the end: x = Some(5), y = 10`. + +In order to make a `match` expression that compares the values of the outer `x` +and `y` rather than introducing a shadowed variable, we would need to use a +match guard conditional instead. We'll be talking about match guards later in +this section. + +### Multiple patterns + +In `match` expressions only, you can match multiple patterns with `|`, which +means *or*: + +```rust +let x = 1; + +match x { + 1 | 2 => println!("one or two"), + 3 => println!("three"), + _ => println!("anything"), +} +``` + +This prints `one or two`. + +### Matching Ranges of Values with `...` + +You can match an inclusive range of values with `...`: + +```rust +let x = 5; + +match x { + 1 ... 5 => println!("one through five"), + _ => println!("something else"), +} +``` + +If `x` is 1, 2, 3, 4, or 5, the first arm will match. + +Ranges are only allowed with numeric values or `char` values. Here's an example +using ranges of `char` values: + +```rust +let x = 'c'; + +match x { + 'a' ... 'j' => println!("early ASCII letter"), + 'k' ... 'z' => println!("late ASCII letter"), + _ => println!("something else"), +} +``` + +This will print `early ASCII letter`. + +### Destructuring to Break Apart Values + +Patterns can be used to *destructure* structs, enums, tuples, and references. +Destructuring means to break a value up into its component pieces. Listing +18-11 shows a `Point` struct with two fields, `x` and `y`, that we can break +apart by using a pattern with a `let` statement: + +Filename: src/main.rs + +```rust +struct Point { + x: i32, + y: i32, +} + +fn main() { + let p = Point { x: 0, y: 7 }; + + let Point { x, y } = p; + assert_eq!(0, x); + assert_eq!(7, y); +} +``` + +Listing 18-11: Destructuring using struct field +shorthand + +This creates the variables `x` and `y` that match the `x` and `y` of `p`. The +names of the variables must match the names of the fields to use this +shorthand. If we wanted to use names different than the variable names, we can +specify `field_name: variable_name` in the pattern. In Listing 18-12, `a` will +have the value in the `Point` instance's `x` field and `b` will have the value +in the `y` field: + +Filename: src/main.rs + +```rust +struct Point { + x: i32, + y: i32, +} + +fn main() { + let p = Point { x: 0, y: 7 }; + + let Point { x: a, y: b } = p; + assert_eq!(0, a); + assert_eq!(7, b); +} +``` + +Listing 18-12: Destructuring struct fields into variables +with different names than the fields + +We can also use destructuring with literal values in order to test and use +inner parts of a value. Listing 18-13 shows a `match` statement that determines +whether a point lies directly on the `x` axis (which is true when `y = 0`), on +the `y` axis (`x = 0`), or neither: + +```rust +# struct Point { +# x: i32, +# y: i32, +# } +# +fn main() { + let p = Point { x: 0, y: 7 }; + + match p { + Point { x, y: 0 } => println!("On the x axis at {}", x), + Point { x: 0, y } => println!("On the y axis at {}", y), + Point { x, y } => println!("On neither axis: ({}, {})", x, y), + } +} +``` + +Listing 18-13: Destructuring and matching literal values +in one pattern + +This will print `On the y axis at 7` since the value `p` matches the second arm +by virtue of `x` having the value 0. + +We used destructuring on enums in Chapter 6, such as in Listing 6-5 where we +destructured an `Option` using a `match` expression and added one to the +inner value of the `Some` variant. + +When the value we're matching against a pattern contains a reference, we can +specify a `&` in the pattern in order to separate the reference and the value. +This is especially useful in closures used with iterators that iterate over +references to values when we want to use the values in the closure rather than +the references. Listing 18-14 shows how to iterate over references to `Point` +instances in a vector, and destructure both the reference and the struct in +order to be able to perform calculations on the `x` and `y` values easily: + +```rust +# struct Point { +# x: i32, +# y: i32, +# } +# +let points = vec![ + Point { x: 0, y: 0 }, + Point { x: 1, y: 5 }, + Point { x: 10, y: -3 }, +]; +let sum_of_squares: i32 = points + .iter() + .map(|&Point {x, y}| x * x + y * y) + .sum(); +``` + +Listing 18-14: Destructuring a reference to a struct into +the struct field values + +Because `iter` iterates over references to the items in the vector, if we +forgot the `&` in the closure arguments in the `map`, we'd get a type mismatch +error like this: + +```text +error[E0308]: mismatched types + --> + | +14 | .map(|Point {x, y}| x * x + y * y) + | ^^^^^^^^^^^^ expected &Point, found struct `Point` + | + = note: expected type `&Point` + found type `Point` +``` + +This says Rust was expecting our closure to match `&Point`, but we tried to +match the value with a pattern that was a `Point` value, not a reference to a +`Point`. + +We can mix, match, and nest destructuring patterns in even more complex ways: +we can do something complicated like this example where we nest structs and +tuples inside of a tuple and destructure all the primitive values out: + +```rust +# struct Point { +# x: i32, +# y: i32, +# } +# +let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 }); +``` + +This lets us break complex types into their component parts. + +### Ignoring Values in a Pattern + +There are a few ways to ignore entire values or parts of values: using the `_` +pattern, using the `_` pattern within another pattern, using a name that starts +with an underscore, or using `..` to ignore all remaining parts of a value. +Let's explore how and why to do each of these. + +#### Ignoring an Entire Value with `_` + +We've seen the use of underscore as a wildcard pattern that will match any value +but not bind to the value. While the underscore pattern is especially useful as +the last arm in a `match` expression, we can use it in any pattern, such as +function arguments as shown in Listing 18-15: + +```rust +fn foo(_: i32) { + // code goes here +} +``` + +Listing 18-15: Using `_` in a function signature + +Normally, you would change the signature to not have the unused parameter. In +cases such as implementing a trait, where you need a certain type signature, +using an underscore lets you ignore a parameter, and the compiler won't warn +about unused function parameters like it would if we had used a name instead. + +#### Ignoring Parts of a Value with a Nested `_` + +We can also use `_` inside of another pattern to ignore just part of a value. +In Listing 18-16, the first `match` arm's pattern matches a `Some` value but +ignores the value inside of the `Some` variant as specified by the underscore: + +```rust +let x = Some(5); + +match x { + Some(_) => println!("got a Some and I don't care what's inside"), + None => (), +} +``` + +Listing 18-16: Ignoring the value inside of the `Some` +variant by using a nested underscore + +This is useful when the code associated with the `match` arm doesn't use the +nested part of the variable at all. + +We can also use underscores in multiple places within one pattern, as shown in +Listing 18-17 where we're ignoring the second and fourth values in a tuple of +five items: + +```rust +let numbers = (2, 4, 8, 16, 32); + +match numbers { + (first, _, third, _, fifth) => { + println!("Some numbers: {}, {}, {}", first, third, fifth) + }, +} +``` + +Listing 18-17: Ignoring multiple parts of a tuple + +This will print `Some numbers: 2, 8, 32`, and the values 4 and 16 will be +ignored. + +#### Ignoring an Unused Variable by Starting its Name with an Underscore + +Usually, Rust will warn you if you create a variable but don't use it anywhere, +since that could be a bug. If you're prototyping or just starting a project, +though, you might create a variable that you'll use eventually, but temporarily +it will be unused. If you're in this situation and would like to tell Rust not +to warn you about the unused variable, you can start the name of the variable +with an underscore. This works just like a variable name in any pattern, only +Rust won't warn you if the variable goes unused. In Listing 18-18, we +do get a warning about not using the variable `y`, but we don't get a warning +about not using the variable `_x`: + +```rust +fn main() { + let _x = 5; + let y = 10; +} +``` + +Listing 18-18: Starting a variable name with an underscore +in order to not get unused variable warnings + +Note that there is a subtle difference between using only `_` and using a name +that starts with an underscore like `_x`: `_x` still binds the value to the +variable, but `_` doesn't bind at all. + +Listing 18-19 shows a case where this distinction matters: `s` will still be +moved into `_s`, which prevents us from using `s` again: + +```rust,ignore +let s = Some(String::from("Hello!")); + +if let Some(_s) = s { + println!("found a string"); +} + +println!("{:?}", s); +``` + +Listing 18-19: An unused variable starting with an +underscore still binds the value, which may take ownership of the value + +Using underscore by itself, however, doesn't ever bind to the value. Listing +18-20 will compile without any errors since `s` does not get moved into `_`: + +```rust +let s = Some(String::from("Hello!")); + +if let Some(_) = s { + println!("found a string"); +} + +println!("{:?}", s); +``` + +Listing 18-20: Using underscore does not bind the +value + +This works just fine. Because we never bind `s` to anything, it's not moved. + +#### Ignoring Remaining Parts of a Value with `..` + +With values that have many parts, we can extract only a few parts and avoid +having to list underscores for each remaining part by instead using `..`. The +`..` pattern will ignore any parts of a value that we haven't explicitly +matched in the rest of the pattern. In Listing 18-21, we have a `Point` struct +that holds a coordinate in three dimensional space. In the `match` expression, +we only want to operate on the `x` coordinate and ignore the values in the `y` +and `z` fields: + +```rust +struct Point { + x: i32, + y: i32, + z: i32, +} + +let origin = Point { x: 0, y: 0, z: 0 }; + +match origin { + Point { x, .. } => println!("x is {}", x), +} +``` + +Listing 18-21: Ignoring all fields of a `Point` except +for `x` by using `..` + +Using `..` is shorter to type than having to list out `y: _` and `z: _`. The +`..` pattern is especially useful when working with structs that have lots of +fields in situations where only one or two fields are relevant. + +`..` will expand to as many values as it needs to be. Listing 18-22 shows a use +of `..` with a tuple: + +```rust +fn main() { + let numbers = (2, 4, 8, 16, 32); + + match numbers { + (first, .., last) => { + println!("Some numbers: {}, {}", first, last); + }, + } +} +``` + +Listing 18-22: Matching only the first and last values in +a tuple and ignoring all other values with `..` + +Here, we have the first and last value matched, with `first` and `last`. The +`..` will match and ignore all of the things in the middle. + +Using `..` must be unambiguous, however. Listing 18-23 shows an example where +it's not clear to Rust which values we want to match and which values we want +to ignore: + +```rust,ignore +fn main() { + let numbers = (2, 4, 8, 16, 32); + + match numbers { + (.., second, ..) => { + println!("Some numbers: {}", second) + }, + } +} +``` + +Listing 18-23: An attempt to use `..` in a way that is +ambiguous + +If we compile this example, we get this error: + +```text +error: `..` can only be used once per tuple or tuple struct pattern + --> src/main.rs:5:22 + | +5 | (.., second, ..) => { + | ^^ +``` + +It's not possible to determine how many values in the tuple should be ignored +before one value is matched with `second`, and then how many further values are +ignored after that. We could mean that we want to ignore 2, bind `second` to 4, +then ignore 8, 16, and 32, or we could mean that we want to ignore 2 and 4, +bind `second` to 8, then ignore 16 and 32, and so forth. The variable name +`second` doesn't mean anything special to Rust, so we get a compiler error +since using `..` in two places like this is ambiguous. + +### `ref` and `ref mut` to Create References in Patterns + +Usually, when you match against a pattern, the variables that the pattern +introduces are bound to a value. This means you'll end up moving the value into +the `match` (or wherever you're using the pattern) since the ownership rules +apply. Listing 18-24 shows an example: + +```rust,ignore +let robot_name = Some(String::from("Bors")); + +match robot_name { + Some(name) => println!("Found a name: {}", name), + None => (), +} + +println!("robot_name is: {:?}", robot_name); +``` + +Listing 18-24: Creating a variable in a match arm pattern +takes ownership of the value + +This example will fail to compile since the value inside the `Some` value in +`robot_name` is moved within the `match` when `name` binds to that value. + +Using `&` in a pattern matches an existing reference in the value, as we saw in +the "Destructuring to Break Apart Values" section. If you want to create a +reference instead in order to borrow the value in a pattern variable, use the +`ref` keyword before the new variable, as shown in Listing 18-25: + +```rust +let robot_name = Some(String::from("Bors")); + +match robot_name { + Some(ref name) => println!("Found a name: {}", name), + None => (), +} + +println!("robot_name is: {:?}", robot_name); +``` + +Listing 18-25: Creating a reference so that a pattern +variable does not take ownership of a value + +This example will compile because the value in the `Some` variant in +`robot_name` is not moved into the `Some(ref name)` arm of the match; the match +only took a reference to the data in `robot_name` rather than moving it. + +To create a mutable reference, use `ref mut` for the same reason as shown in +Listing 18-26: + +```rust +let mut robot_name = Some(String::from("Bors")); + +match robot_name { + Some(ref mut name) => *name = String::from("Another name"), + None => (), +} + +println!("robot_name is: {:?}", robot_name); +``` + +Listing 18-26: Creating a mutable reference to a value as +part of a pattern using `ref mut` + +This example will compile and print `robot_name is: Some("Another name")`. +Since `name` is a mutable reference, within the match arm code, we need to +dereference using the `*` operator in order to be able to mutate the value. + +### Extra Conditionals with Match Guards + +You can introduce *match guards* as part of a match arm by specifying an +additional `if` conditional after the pattern. The conditional can use +variables created in the pattern. Listing 18-27 has a `match` expression with a +match guard in the first arm: + +```rust +let num = Some(4); + +match num { + Some(x) if x < 5 => println!("less than five: {}", x), + Some(x) => println!("{}", x), + None => (), +} +``` + +Listing 18-27: Adding a match guard to a pattern + +This example will print `less than five: 4`. If `num` was instead `Some(7)`, +this example would print `7`. Match guards allow you to express more complexity +than patterns alone give you. + +In Listing 18-10, we saw that since patterns shadow variables, we weren't able +to specify a pattern to express the case when a value was equal to a variable +outside the `match`. Listing 18-28 shows how we can use a match guard to +accomplish this: + +```rust +fn main() { + let x = Some(5); + let y = 10; + + match x { + Some(50) => println!("Got 50"), + Some(n) if n == y => println!("Matched, n = {:?}", n), + _ => println!("Default case, x = {:?}", x), + } + + println!("at the end: x = {:?}, y = {:?}", x, y); +} +``` + +Listing 18-28: Using a match guard to test for equality +with an outer variable + +This will now print `Default case, x = Some(5)`. Because the second match arm +is not introducing a new variable `y` that shadows the outer `y` in the +pattern, we can use `y` in the match guard. We're still destructuring `x` to +get the inner value `n`, and then we can compare `n` and `y` in the match guard. + +If you're using a match guard with multiple patterns specified by `|`, the +match guard condition applies to all of the patterns. Listing 18-29 shows a +match guard that applies to the value matched by all three patterns in the +first arm: + +```rust +let x = 4; +let y = false; + +match x { + 4 | 5 | 6 if y => println!("yes"), + _ => println!("no"), +} +``` + +Listing 18-29: Combining multiple patterns with a match +guard + +This prints `no` since the `if` condition applies to the whole pattern `4 | 5 | +6`, not only to the last value `6`. In other words, the precedence of a match +guard in relation to a pattern behaves like this: + +```text +(4 | 5 | 6) if y => ... +``` + +rather than this: + +```text +4 | 5 | (6 if y) => ... +``` + +### `@` Bindings + +In order to test a value in a pattern but also be able to create a variable +bound to the value, we can use `@`. Listing 18-30 shows an example where we +want to test that a `Message::Hello` `id` field is within the range `3...7` but +also be able to bind to the value so that we can use it in the code associated +with the arm: + +```rust +enum Message { + Hello { id: i32 }, +} + +let msg = Message::Hello { id: 5 }; + +match msg { + Message::Hello { id: id @ 3...7 } => { + println!("Found an id in range: {}", id) + }, + Message::Hello { id: 10...12 } => { + println!("Found an id in another range") + }, + Message::Hello { id } => { + println!("Found some other id: {}", id) + }, +} +``` + +Listing 18-30: Using `@` to bind to a value in a pattern +while also testing it + +This example will print `Found an id in range: 5`. By specifying `id @` before +the range, we're capturing whatever value matched the range while also testing +it. In the second arm where we only have a range specified in the pattern, the +code associated with the arm doesn't know if `id` is 10, 11, or 12, since we +haven't saved the `id` value in a variable: we only know that the value matched +something in that range if that arm's code is executed. In the last arm where +we've specified a variable without a range, we do have the value available to +use in the arm's code, but we haven't applied any other test to the value. +Using `@` lets us test a value and save it in a variable within one pattern. + +## Summary + +Patterns are a useful feature of Rust that help to distinguish between +different kinds of data. When used in `match` statements, Rust makes sure that +your patterns cover every possible value. Patterns in `let` statements and +function parameters make those constructs more powerful, enabling the +destructuring of values into smaller parts at the same time as assigning to +variables. + +Now, for the penultimate chapter of the book, let's take a look at some +advanced parts of a variety of Rust's features. From a3fe4f892ecff6b20bd951c4c43d79c236d28093 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 26 Jun 2017 11:12:20 +0800 Subject: [PATCH 22/59] Delete ch18-03-pattern-syntax.md --- ch18-03-pattern-syntax.md | 697 -------------------------------------- 1 file changed, 697 deletions(-) delete mode 100644 ch18-03-pattern-syntax.md diff --git a/ch18-03-pattern-syntax.md b/ch18-03-pattern-syntax.md deleted file mode 100644 index 795e221..0000000 --- a/ch18-03-pattern-syntax.md +++ /dev/null @@ -1,697 +0,0 @@ -## All the Pattern Syntax - -We've seen some examples of different kinds of patterns throughout the book. -This section lists all the syntax valid in patterns and why you might want to -use each of them. - -### Literals - -As we saw in Chapter 6, you can match against literals directly: - -```rust -let x = 1; - -match x { - 1 => println!("one"), - 2 => println!("two"), - 3 => println!("three"), - _ => println!("anything"), -} -``` - -This prints `one` since the value in `x` is 1. - -### Named Variables - -Named variables are irrefutable patterns that match any value. - -As with all variables, variables declared as part of a pattern will shadow -variables with the same name outside of the `match` construct since a `match` -starts a new scope. In Listing 18-10, we declare a variable named `x` with the -value `Some(5)` and a variable `y` with the value `10`. Then we have a `match` -expression on the value `x`. Take a look at the patterns in the match arms and -the `println!` at the end, and make a guess about what will be printed before -running this code or reading further: - -Filename: src/main.rs - -```rust -fn main() { - let x = Some(5); - let y = 10; - - match x { - Some(50) => println!("Got 50"), - Some(y) => println!("Matched, y = {:?}", y), - _ => println!("Default case, x = {:?}", x), - } - - println!("at the end: x = {:?}, y = {:?}", x, y); -} -``` - -Listing 18-10: A `match` statement with an arm that -introduces a shadowed variable `y` - - - -Let's walk through what happens when the `match` statement runs. The first -match arm has the pattern `Some(50)`, and the value in `x` (`Some(5)`) does not -match `Some(50)`, so we continue. In the second match arm, the pattern -`Some(y)` introduces a new variable name `y` that will match any value inside a -`Some` value. Because we're in a new scope inside the `match` expression, this -is a new variable, not the `y` we declared at the beginning that has the -value 10. The new `y` binding will match any value inside a `Some`, which is -what we have in `x`, so we execute the expression for that arm and print -`Matched, y = 5` since this `y` binds to the inner value of the `Some` in `x`, -which is 5. - -If `x` had been a `None` value instead of `Some(5)`, we would have matched the -underscore since the other two arms' patterns would not have matched. In the -expression for that match arm, since we did not introduce an `x` variable in -the pattern of the arm, this `x` is still the outer `x` that has not been -shadowed. In this hypothetical case, the `match` would print `Default case, x = -None`. - -Once the `match` expression is over, its scope ends, and so does the scope of -the inner `y`. The last `println!` produces `at the end: x = Some(5), y = 10`. - -In order to make a `match` expression that compares the values of the outer `x` -and `y` rather than introducing a shadowed variable, we would need to use a -match guard conditional instead. We'll be talking about match guards later in -this section. - -### Multiple patterns - -In `match` expressions only, you can match multiple patterns with `|`, which -means *or*: - -```rust -let x = 1; - -match x { - 1 | 2 => println!("one or two"), - 3 => println!("three"), - _ => println!("anything"), -} -``` - -This prints `one or two`. - -### Matching Ranges of Values with `...` - -You can match an inclusive range of values with `...`: - -```rust -let x = 5; - -match x { - 1 ... 5 => println!("one through five"), - _ => println!("something else"), -} -``` - -If `x` is 1, 2, 3, 4, or 5, the first arm will match. - -Ranges are only allowed with numeric values or `char` values. Here's an example -using ranges of `char` values: - -```rust -let x = 'c'; - -match x { - 'a' ... 'j' => println!("early ASCII letter"), - 'k' ... 'z' => println!("late ASCII letter"), - _ => println!("something else"), -} -``` - -This will print `early ASCII letter`. - -### Destructuring to Break Apart Values - -Patterns can be used to *destructure* structs, enums, tuples, and references. -Destructuring means to break a value up into its component pieces. Listing -18-11 shows a `Point` struct with two fields, `x` and `y`, that we can break -apart by using a pattern with a `let` statement: - -Filename: src/main.rs - -```rust -struct Point { - x: i32, - y: i32, -} - -fn main() { - let p = Point { x: 0, y: 7 }; - - let Point { x, y } = p; - assert_eq!(0, x); - assert_eq!(7, y); -} -``` - -Listing 18-11: Destructuring using struct field -shorthand - -This creates the variables `x` and `y` that match the `x` and `y` of `p`. The -names of the variables must match the names of the fields to use this -shorthand. If we wanted to use names different than the variable names, we can -specify `field_name: variable_name` in the pattern. In Listing 18-12, `a` will -have the value in the `Point` instance's `x` field and `b` will have the value -in the `y` field: - -Filename: src/main.rs - -```rust -struct Point { - x: i32, - y: i32, -} - -fn main() { - let p = Point { x: 0, y: 7 }; - - let Point { x: a, y: b } = p; - assert_eq!(0, a); - assert_eq!(7, b); -} -``` - -Listing 18-12: Destructuring struct fields into variables -with different names than the fields - -We can also use destructuring with literal values in order to test and use -inner parts of a value. Listing 18-13 shows a `match` statement that determines -whether a point lies directly on the `x` axis (which is true when `y = 0`), on -the `y` axis (`x = 0`), or neither: - -```rust -# struct Point { -# x: i32, -# y: i32, -# } -# -fn main() { - let p = Point { x: 0, y: 7 }; - - match p { - Point { x, y: 0 } => println!("On the x axis at {}", x), - Point { x: 0, y } => println!("On the y axis at {}", y), - Point { x, y } => println!("On neither axis: ({}, {})", x, y), - } -} -``` - -Listing 18-13: Destructuring and matching literal values -in one pattern - -This will print `On the y axis at 7` since the value `p` matches the second arm -by virtue of `x` having the value 0. - -We used destructuring on enums in Chapter 6, such as in Listing 6-5 where we -destructured an `Option` using a `match` expression and added one to the -inner value of the `Some` variant. - -When the value we're matching against a pattern contains a reference, we can -specify a `&` in the pattern in order to separate the reference and the value. -This is especially useful in closures used with iterators that iterate over -references to values when we want to use the values in the closure rather than -the references. Listing 18-14 shows how to iterate over references to `Point` -instances in a vector, and destructure both the reference and the struct in -order to be able to perform calculations on the `x` and `y` values easily: - -```rust -# struct Point { -# x: i32, -# y: i32, -# } -# -let points = vec![ - Point { x: 0, y: 0 }, - Point { x: 1, y: 5 }, - Point { x: 10, y: -3 }, -]; -let sum_of_squares: i32 = points - .iter() - .map(|&Point {x, y}| x * x + y * y) - .sum(); -``` - -Listing 18-14: Destructuring a reference to a struct into -the struct field values - -Because `iter` iterates over references to the items in the vector, if we -forgot the `&` in the closure arguments in the `map`, we'd get a type mismatch -error like this: - -```text -error[E0308]: mismatched types - --> - | -14 | .map(|Point {x, y}| x * x + y * y) - | ^^^^^^^^^^^^ expected &Point, found struct `Point` - | - = note: expected type `&Point` - found type `Point` -``` - -This says Rust was expecting our closure to match `&Point`, but we tried to -match the value with a pattern that was a `Point` value, not a reference to a -`Point`. - -We can mix, match, and nest destructuring patterns in even more complex ways: -we can do something complicated like this example where we nest structs and -tuples inside of a tuple and destructure all the primitive values out: - -```rust -# struct Point { -# x: i32, -# y: i32, -# } -# -let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 }); -``` - -This lets us break complex types into their component parts. - -### Ignoring Values in a Pattern - -There are a few ways to ignore entire values or parts of values: using the `_` -pattern, using the `_` pattern within another pattern, using a name that starts -with an underscore, or using `..` to ignore all remaining parts of a value. -Let's explore how and why to do each of these. - -#### Ignoring an Entire Value with `_` - -We've seen the use of underscore as a wildcard pattern that will match any value -but not bind to the value. While the underscore pattern is especially useful as -the last arm in a `match` expression, we can use it in any pattern, such as -function arguments as shown in Listing 18-15: - -```rust -fn foo(_: i32) { - // code goes here -} -``` - -Listing 18-15: Using `_` in a function signature - -Normally, you would change the signature to not have the unused parameter. In -cases such as implementing a trait, where you need a certain type signature, -using an underscore lets you ignore a parameter, and the compiler won't warn -about unused function parameters like it would if we had used a name instead. - -#### Ignoring Parts of a Value with a Nested `_` - -We can also use `_` inside of another pattern to ignore just part of a value. -In Listing 18-16, the first `match` arm's pattern matches a `Some` value but -ignores the value inside of the `Some` variant as specified by the underscore: - -```rust -let x = Some(5); - -match x { - Some(_) => println!("got a Some and I don't care what's inside"), - None => (), -} -``` - -Listing 18-16: Ignoring the value inside of the `Some` -variant by using a nested underscore - -This is useful when the code associated with the `match` arm doesn't use the -nested part of the variable at all. - -We can also use underscores in multiple places within one pattern, as shown in -Listing 18-17 where we're ignoring the second and fourth values in a tuple of -five items: - -```rust -let numbers = (2, 4, 8, 16, 32); - -match numbers { - (first, _, third, _, fifth) => { - println!("Some numbers: {}, {}, {}", first, third, fifth) - }, -} -``` - -Listing 18-17: Ignoring multiple parts of a tuple - -This will print `Some numbers: 2, 8, 32`, and the values 4 and 16 will be -ignored. - -#### Ignoring an Unused Variable by Starting its Name with an Underscore - -Usually, Rust will warn you if you create a variable but don't use it anywhere, -since that could be a bug. If you're prototyping or just starting a project, -though, you might create a variable that you'll use eventually, but temporarily -it will be unused. If you're in this situation and would like to tell Rust not -to warn you about the unused variable, you can start the name of the variable -with an underscore. This works just like a variable name in any pattern, only -Rust won't warn you if the variable goes unused. In Listing 18-18, we -do get a warning about not using the variable `y`, but we don't get a warning -about not using the variable `_x`: - -```rust -fn main() { - let _x = 5; - let y = 10; -} -``` - -Listing 18-18: Starting a variable name with an underscore -in order to not get unused variable warnings - -Note that there is a subtle difference between using only `_` and using a name -that starts with an underscore like `_x`: `_x` still binds the value to the -variable, but `_` doesn't bind at all. - -Listing 18-19 shows a case where this distinction matters: `s` will still be -moved into `_s`, which prevents us from using `s` again: - -```rust,ignore -let s = Some(String::from("Hello!")); - -if let Some(_s) = s { - println!("found a string"); -} - -println!("{:?}", s); -``` - -Listing 18-19: An unused variable starting with an -underscore still binds the value, which may take ownership of the value - -Using underscore by itself, however, doesn't ever bind to the value. Listing -18-20 will compile without any errors since `s` does not get moved into `_`: - -```rust -let s = Some(String::from("Hello!")); - -if let Some(_) = s { - println!("found a string"); -} - -println!("{:?}", s); -``` - -Listing 18-20: Using underscore does not bind the -value - -This works just fine. Because we never bind `s` to anything, it's not moved. - -#### Ignoring Remaining Parts of a Value with `..` - -With values that have many parts, we can extract only a few parts and avoid -having to list underscores for each remaining part by instead using `..`. The -`..` pattern will ignore any parts of a value that we haven't explicitly -matched in the rest of the pattern. In Listing 18-21, we have a `Point` struct -that holds a coordinate in three dimensional space. In the `match` expression, -we only want to operate on the `x` coordinate and ignore the values in the `y` -and `z` fields: - -```rust -struct Point { - x: i32, - y: i32, - z: i32, -} - -let origin = Point { x: 0, y: 0, z: 0 }; - -match origin { - Point { x, .. } => println!("x is {}", x), -} -``` - -Listing 18-21: Ignoring all fields of a `Point` except -for `x` by using `..` - -Using `..` is shorter to type than having to list out `y: _` and `z: _`. The -`..` pattern is especially useful when working with structs that have lots of -fields in situations where only one or two fields are relevant. - -`..` will expand to as many values as it needs to be. Listing 18-22 shows a use -of `..` with a tuple: - -```rust -fn main() { - let numbers = (2, 4, 8, 16, 32); - - match numbers { - (first, .., last) => { - println!("Some numbers: {}, {}", first, last); - }, - } -} -``` - -Listing 18-22: Matching only the first and last values in -a tuple and ignoring all other values with `..` - -Here, we have the first and last value matched, with `first` and `last`. The -`..` will match and ignore all of the things in the middle. - -Using `..` must be unambiguous, however. Listing 18-23 shows an example where -it's not clear to Rust which values we want to match and which values we want -to ignore: - -```rust,ignore -fn main() { - let numbers = (2, 4, 8, 16, 32); - - match numbers { - (.., second, ..) => { - println!("Some numbers: {}", second) - }, - } -} -``` - -Listing 18-23: An attempt to use `..` in a way that is -ambiguous - -If we compile this example, we get this error: - -```text -error: `..` can only be used once per tuple or tuple struct pattern - --> src/main.rs:5:22 - | -5 | (.., second, ..) => { - | ^^ -``` - -It's not possible to determine how many values in the tuple should be ignored -before one value is matched with `second`, and then how many further values are -ignored after that. We could mean that we want to ignore 2, bind `second` to 4, -then ignore 8, 16, and 32, or we could mean that we want to ignore 2 and 4, -bind `second` to 8, then ignore 16 and 32, and so forth. The variable name -`second` doesn't mean anything special to Rust, so we get a compiler error -since using `..` in two places like this is ambiguous. - -### `ref` and `ref mut` to Create References in Patterns - -Usually, when you match against a pattern, the variables that the pattern -introduces are bound to a value. This means you'll end up moving the value into -the `match` (or wherever you're using the pattern) since the ownership rules -apply. Listing 18-24 shows an example: - -```rust,ignore -let robot_name = Some(String::from("Bors")); - -match robot_name { - Some(name) => println!("Found a name: {}", name), - None => (), -} - -println!("robot_name is: {:?}", robot_name); -``` - -Listing 18-24: Creating a variable in a match arm pattern -takes ownership of the value - -This example will fail to compile since the value inside the `Some` value in -`robot_name` is moved within the `match` when `name` binds to that value. - -Using `&` in a pattern matches an existing reference in the value, as we saw in -the "Destructuring to Break Apart Values" section. If you want to create a -reference instead in order to borrow the value in a pattern variable, use the -`ref` keyword before the new variable, as shown in Listing 18-25: - -```rust -let robot_name = Some(String::from("Bors")); - -match robot_name { - Some(ref name) => println!("Found a name: {}", name), - None => (), -} - -println!("robot_name is: {:?}", robot_name); -``` - -Listing 18-25: Creating a reference so that a pattern -variable does not take ownership of a value - -This example will compile because the value in the `Some` variant in -`robot_name` is not moved into the `Some(ref name)` arm of the match; the match -only took a reference to the data in `robot_name` rather than moving it. - -To create a mutable reference, use `ref mut` for the same reason as shown in -Listing 18-26: - -```rust -let mut robot_name = Some(String::from("Bors")); - -match robot_name { - Some(ref mut name) => *name = String::from("Another name"), - None => (), -} - -println!("robot_name is: {:?}", robot_name); -``` - -Listing 18-26: Creating a mutable reference to a value as -part of a pattern using `ref mut` - -This example will compile and print `robot_name is: Some("Another name")`. -Since `name` is a mutable reference, within the match arm code, we need to -dereference using the `*` operator in order to be able to mutate the value. - -### Extra Conditionals with Match Guards - -You can introduce *match guards* as part of a match arm by specifying an -additional `if` conditional after the pattern. The conditional can use -variables created in the pattern. Listing 18-27 has a `match` expression with a -match guard in the first arm: - -```rust -let num = Some(4); - -match num { - Some(x) if x < 5 => println!("less than five: {}", x), - Some(x) => println!("{}", x), - None => (), -} -``` - -Listing 18-27: Adding a match guard to a pattern - -This example will print `less than five: 4`. If `num` was instead `Some(7)`, -this example would print `7`. Match guards allow you to express more complexity -than patterns alone give you. - -In Listing 18-10, we saw that since patterns shadow variables, we weren't able -to specify a pattern to express the case when a value was equal to a variable -outside the `match`. Listing 18-28 shows how we can use a match guard to -accomplish this: - -```rust -fn main() { - let x = Some(5); - let y = 10; - - match x { - Some(50) => println!("Got 50"), - Some(n) if n == y => println!("Matched, n = {:?}", n), - _ => println!("Default case, x = {:?}", x), - } - - println!("at the end: x = {:?}, y = {:?}", x, y); -} -``` - -Listing 18-28: Using a match guard to test for equality -with an outer variable - -This will now print `Default case, x = Some(5)`. Because the second match arm -is not introducing a new variable `y` that shadows the outer `y` in the -pattern, we can use `y` in the match guard. We're still destructuring `x` to -get the inner value `n`, and then we can compare `n` and `y` in the match guard. - -If you're using a match guard with multiple patterns specified by `|`, the -match guard condition applies to all of the patterns. Listing 18-29 shows a -match guard that applies to the value matched by all three patterns in the -first arm: - -```rust -let x = 4; -let y = false; - -match x { - 4 | 5 | 6 if y => println!("yes"), - _ => println!("no"), -} -``` - -Listing 18-29: Combining multiple patterns with a match -guard - -This prints `no` since the `if` condition applies to the whole pattern `4 | 5 | -6`, not only to the last value `6`. In other words, the precedence of a match -guard in relation to a pattern behaves like this: - -```text -(4 | 5 | 6) if y => ... -``` - -rather than this: - -```text -4 | 5 | (6 if y) => ... -``` - -### `@` Bindings - -In order to test a value in a pattern but also be able to create a variable -bound to the value, we can use `@`. Listing 18-30 shows an example where we -want to test that a `Message::Hello` `id` field is within the range `3...7` but -also be able to bind to the value so that we can use it in the code associated -with the arm: - -```rust -enum Message { - Hello { id: i32 }, -} - -let msg = Message::Hello { id: 5 }; - -match msg { - Message::Hello { id: id @ 3...7 } => { - println!("Found an id in range: {}", id) - }, - Message::Hello { id: 10...12 } => { - println!("Found an id in another range") - }, - Message::Hello { id } => { - println!("Found some other id: {}", id) - }, -} -``` - -Listing 18-30: Using `@` to bind to a value in a pattern -while also testing it - -This example will print `Found an id in range: 5`. By specifying `id @` before -the range, we're capturing whatever value matched the range while also testing -it. In the second arm where we only have a range specified in the pattern, the -code associated with the arm doesn't know if `id` is 10, 11, or 12, since we -haven't saved the `id` value in a variable: we only know that the value matched -something in that range if that arm's code is executed. In the last arm where -we've specified a variable without a range, we do have the value available to -use in the arm's code, but we haven't applied any other test to the value. -Using `@` lets us test a value and save it in a variable within one pattern. - -## Summary - -Patterns are a useful feature of Rust that help to distinguish between -different kinds of data. When used in `match` statements, Rust makes sure that -your patterns cover every possible value. Patterns in `let` statements and -function parameters make those constructs more powerful, enabling the -destructuring of values into smaller parts at the same time as assigning to -variables. - -Now, for the penultimate chapter of the book, let's take a look at some -advanced parts of a variety of Rust's features. From 68aedfe139b431d098133cbc48590c66b8021385 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 26 Jun 2017 11:12:49 +0800 Subject: [PATCH 23/59] Pattern Syntax. --- src/ch18-03-pattern-syntax.md | 697 ++++++++++++++++++++++++++++++++++ 1 file changed, 697 insertions(+) create mode 100644 src/ch18-03-pattern-syntax.md diff --git a/src/ch18-03-pattern-syntax.md b/src/ch18-03-pattern-syntax.md new file mode 100644 index 0000000..795e221 --- /dev/null +++ b/src/ch18-03-pattern-syntax.md @@ -0,0 +1,697 @@ +## All the Pattern Syntax + +We've seen some examples of different kinds of patterns throughout the book. +This section lists all the syntax valid in patterns and why you might want to +use each of them. + +### Literals + +As we saw in Chapter 6, you can match against literals directly: + +```rust +let x = 1; + +match x { + 1 => println!("one"), + 2 => println!("two"), + 3 => println!("three"), + _ => println!("anything"), +} +``` + +This prints `one` since the value in `x` is 1. + +### Named Variables + +Named variables are irrefutable patterns that match any value. + +As with all variables, variables declared as part of a pattern will shadow +variables with the same name outside of the `match` construct since a `match` +starts a new scope. In Listing 18-10, we declare a variable named `x` with the +value `Some(5)` and a variable `y` with the value `10`. Then we have a `match` +expression on the value `x`. Take a look at the patterns in the match arms and +the `println!` at the end, and make a guess about what will be printed before +running this code or reading further: + +Filename: src/main.rs + +```rust +fn main() { + let x = Some(5); + let y = 10; + + match x { + Some(50) => println!("Got 50"), + Some(y) => println!("Matched, y = {:?}", y), + _ => println!("Default case, x = {:?}", x), + } + + println!("at the end: x = {:?}, y = {:?}", x, y); +} +``` + +Listing 18-10: A `match` statement with an arm that +introduces a shadowed variable `y` + + + +Let's walk through what happens when the `match` statement runs. The first +match arm has the pattern `Some(50)`, and the value in `x` (`Some(5)`) does not +match `Some(50)`, so we continue. In the second match arm, the pattern +`Some(y)` introduces a new variable name `y` that will match any value inside a +`Some` value. Because we're in a new scope inside the `match` expression, this +is a new variable, not the `y` we declared at the beginning that has the +value 10. The new `y` binding will match any value inside a `Some`, which is +what we have in `x`, so we execute the expression for that arm and print +`Matched, y = 5` since this `y` binds to the inner value of the `Some` in `x`, +which is 5. + +If `x` had been a `None` value instead of `Some(5)`, we would have matched the +underscore since the other two arms' patterns would not have matched. In the +expression for that match arm, since we did not introduce an `x` variable in +the pattern of the arm, this `x` is still the outer `x` that has not been +shadowed. In this hypothetical case, the `match` would print `Default case, x = +None`. + +Once the `match` expression is over, its scope ends, and so does the scope of +the inner `y`. The last `println!` produces `at the end: x = Some(5), y = 10`. + +In order to make a `match` expression that compares the values of the outer `x` +and `y` rather than introducing a shadowed variable, we would need to use a +match guard conditional instead. We'll be talking about match guards later in +this section. + +### Multiple patterns + +In `match` expressions only, you can match multiple patterns with `|`, which +means *or*: + +```rust +let x = 1; + +match x { + 1 | 2 => println!("one or two"), + 3 => println!("three"), + _ => println!("anything"), +} +``` + +This prints `one or two`. + +### Matching Ranges of Values with `...` + +You can match an inclusive range of values with `...`: + +```rust +let x = 5; + +match x { + 1 ... 5 => println!("one through five"), + _ => println!("something else"), +} +``` + +If `x` is 1, 2, 3, 4, or 5, the first arm will match. + +Ranges are only allowed with numeric values or `char` values. Here's an example +using ranges of `char` values: + +```rust +let x = 'c'; + +match x { + 'a' ... 'j' => println!("early ASCII letter"), + 'k' ... 'z' => println!("late ASCII letter"), + _ => println!("something else"), +} +``` + +This will print `early ASCII letter`. + +### Destructuring to Break Apart Values + +Patterns can be used to *destructure* structs, enums, tuples, and references. +Destructuring means to break a value up into its component pieces. Listing +18-11 shows a `Point` struct with two fields, `x` and `y`, that we can break +apart by using a pattern with a `let` statement: + +Filename: src/main.rs + +```rust +struct Point { + x: i32, + y: i32, +} + +fn main() { + let p = Point { x: 0, y: 7 }; + + let Point { x, y } = p; + assert_eq!(0, x); + assert_eq!(7, y); +} +``` + +Listing 18-11: Destructuring using struct field +shorthand + +This creates the variables `x` and `y` that match the `x` and `y` of `p`. The +names of the variables must match the names of the fields to use this +shorthand. If we wanted to use names different than the variable names, we can +specify `field_name: variable_name` in the pattern. In Listing 18-12, `a` will +have the value in the `Point` instance's `x` field and `b` will have the value +in the `y` field: + +Filename: src/main.rs + +```rust +struct Point { + x: i32, + y: i32, +} + +fn main() { + let p = Point { x: 0, y: 7 }; + + let Point { x: a, y: b } = p; + assert_eq!(0, a); + assert_eq!(7, b); +} +``` + +Listing 18-12: Destructuring struct fields into variables +with different names than the fields + +We can also use destructuring with literal values in order to test and use +inner parts of a value. Listing 18-13 shows a `match` statement that determines +whether a point lies directly on the `x` axis (which is true when `y = 0`), on +the `y` axis (`x = 0`), or neither: + +```rust +# struct Point { +# x: i32, +# y: i32, +# } +# +fn main() { + let p = Point { x: 0, y: 7 }; + + match p { + Point { x, y: 0 } => println!("On the x axis at {}", x), + Point { x: 0, y } => println!("On the y axis at {}", y), + Point { x, y } => println!("On neither axis: ({}, {})", x, y), + } +} +``` + +Listing 18-13: Destructuring and matching literal values +in one pattern + +This will print `On the y axis at 7` since the value `p` matches the second arm +by virtue of `x` having the value 0. + +We used destructuring on enums in Chapter 6, such as in Listing 6-5 where we +destructured an `Option` using a `match` expression and added one to the +inner value of the `Some` variant. + +When the value we're matching against a pattern contains a reference, we can +specify a `&` in the pattern in order to separate the reference and the value. +This is especially useful in closures used with iterators that iterate over +references to values when we want to use the values in the closure rather than +the references. Listing 18-14 shows how to iterate over references to `Point` +instances in a vector, and destructure both the reference and the struct in +order to be able to perform calculations on the `x` and `y` values easily: + +```rust +# struct Point { +# x: i32, +# y: i32, +# } +# +let points = vec![ + Point { x: 0, y: 0 }, + Point { x: 1, y: 5 }, + Point { x: 10, y: -3 }, +]; +let sum_of_squares: i32 = points + .iter() + .map(|&Point {x, y}| x * x + y * y) + .sum(); +``` + +Listing 18-14: Destructuring a reference to a struct into +the struct field values + +Because `iter` iterates over references to the items in the vector, if we +forgot the `&` in the closure arguments in the `map`, we'd get a type mismatch +error like this: + +```text +error[E0308]: mismatched types + --> + | +14 | .map(|Point {x, y}| x * x + y * y) + | ^^^^^^^^^^^^ expected &Point, found struct `Point` + | + = note: expected type `&Point` + found type `Point` +``` + +This says Rust was expecting our closure to match `&Point`, but we tried to +match the value with a pattern that was a `Point` value, not a reference to a +`Point`. + +We can mix, match, and nest destructuring patterns in even more complex ways: +we can do something complicated like this example where we nest structs and +tuples inside of a tuple and destructure all the primitive values out: + +```rust +# struct Point { +# x: i32, +# y: i32, +# } +# +let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 }); +``` + +This lets us break complex types into their component parts. + +### Ignoring Values in a Pattern + +There are a few ways to ignore entire values or parts of values: using the `_` +pattern, using the `_` pattern within another pattern, using a name that starts +with an underscore, or using `..` to ignore all remaining parts of a value. +Let's explore how and why to do each of these. + +#### Ignoring an Entire Value with `_` + +We've seen the use of underscore as a wildcard pattern that will match any value +but not bind to the value. While the underscore pattern is especially useful as +the last arm in a `match` expression, we can use it in any pattern, such as +function arguments as shown in Listing 18-15: + +```rust +fn foo(_: i32) { + // code goes here +} +``` + +Listing 18-15: Using `_` in a function signature + +Normally, you would change the signature to not have the unused parameter. In +cases such as implementing a trait, where you need a certain type signature, +using an underscore lets you ignore a parameter, and the compiler won't warn +about unused function parameters like it would if we had used a name instead. + +#### Ignoring Parts of a Value with a Nested `_` + +We can also use `_` inside of another pattern to ignore just part of a value. +In Listing 18-16, the first `match` arm's pattern matches a `Some` value but +ignores the value inside of the `Some` variant as specified by the underscore: + +```rust +let x = Some(5); + +match x { + Some(_) => println!("got a Some and I don't care what's inside"), + None => (), +} +``` + +Listing 18-16: Ignoring the value inside of the `Some` +variant by using a nested underscore + +This is useful when the code associated with the `match` arm doesn't use the +nested part of the variable at all. + +We can also use underscores in multiple places within one pattern, as shown in +Listing 18-17 where we're ignoring the second and fourth values in a tuple of +five items: + +```rust +let numbers = (2, 4, 8, 16, 32); + +match numbers { + (first, _, third, _, fifth) => { + println!("Some numbers: {}, {}, {}", first, third, fifth) + }, +} +``` + +Listing 18-17: Ignoring multiple parts of a tuple + +This will print `Some numbers: 2, 8, 32`, and the values 4 and 16 will be +ignored. + +#### Ignoring an Unused Variable by Starting its Name with an Underscore + +Usually, Rust will warn you if you create a variable but don't use it anywhere, +since that could be a bug. If you're prototyping or just starting a project, +though, you might create a variable that you'll use eventually, but temporarily +it will be unused. If you're in this situation and would like to tell Rust not +to warn you about the unused variable, you can start the name of the variable +with an underscore. This works just like a variable name in any pattern, only +Rust won't warn you if the variable goes unused. In Listing 18-18, we +do get a warning about not using the variable `y`, but we don't get a warning +about not using the variable `_x`: + +```rust +fn main() { + let _x = 5; + let y = 10; +} +``` + +Listing 18-18: Starting a variable name with an underscore +in order to not get unused variable warnings + +Note that there is a subtle difference between using only `_` and using a name +that starts with an underscore like `_x`: `_x` still binds the value to the +variable, but `_` doesn't bind at all. + +Listing 18-19 shows a case where this distinction matters: `s` will still be +moved into `_s`, which prevents us from using `s` again: + +```rust,ignore +let s = Some(String::from("Hello!")); + +if let Some(_s) = s { + println!("found a string"); +} + +println!("{:?}", s); +``` + +Listing 18-19: An unused variable starting with an +underscore still binds the value, which may take ownership of the value + +Using underscore by itself, however, doesn't ever bind to the value. Listing +18-20 will compile without any errors since `s` does not get moved into `_`: + +```rust +let s = Some(String::from("Hello!")); + +if let Some(_) = s { + println!("found a string"); +} + +println!("{:?}", s); +``` + +Listing 18-20: Using underscore does not bind the +value + +This works just fine. Because we never bind `s` to anything, it's not moved. + +#### Ignoring Remaining Parts of a Value with `..` + +With values that have many parts, we can extract only a few parts and avoid +having to list underscores for each remaining part by instead using `..`. The +`..` pattern will ignore any parts of a value that we haven't explicitly +matched in the rest of the pattern. In Listing 18-21, we have a `Point` struct +that holds a coordinate in three dimensional space. In the `match` expression, +we only want to operate on the `x` coordinate and ignore the values in the `y` +and `z` fields: + +```rust +struct Point { + x: i32, + y: i32, + z: i32, +} + +let origin = Point { x: 0, y: 0, z: 0 }; + +match origin { + Point { x, .. } => println!("x is {}", x), +} +``` + +Listing 18-21: Ignoring all fields of a `Point` except +for `x` by using `..` + +Using `..` is shorter to type than having to list out `y: _` and `z: _`. The +`..` pattern is especially useful when working with structs that have lots of +fields in situations where only one or two fields are relevant. + +`..` will expand to as many values as it needs to be. Listing 18-22 shows a use +of `..` with a tuple: + +```rust +fn main() { + let numbers = (2, 4, 8, 16, 32); + + match numbers { + (first, .., last) => { + println!("Some numbers: {}, {}", first, last); + }, + } +} +``` + +Listing 18-22: Matching only the first and last values in +a tuple and ignoring all other values with `..` + +Here, we have the first and last value matched, with `first` and `last`. The +`..` will match and ignore all of the things in the middle. + +Using `..` must be unambiguous, however. Listing 18-23 shows an example where +it's not clear to Rust which values we want to match and which values we want +to ignore: + +```rust,ignore +fn main() { + let numbers = (2, 4, 8, 16, 32); + + match numbers { + (.., second, ..) => { + println!("Some numbers: {}", second) + }, + } +} +``` + +Listing 18-23: An attempt to use `..` in a way that is +ambiguous + +If we compile this example, we get this error: + +```text +error: `..` can only be used once per tuple or tuple struct pattern + --> src/main.rs:5:22 + | +5 | (.., second, ..) => { + | ^^ +``` + +It's not possible to determine how many values in the tuple should be ignored +before one value is matched with `second`, and then how many further values are +ignored after that. We could mean that we want to ignore 2, bind `second` to 4, +then ignore 8, 16, and 32, or we could mean that we want to ignore 2 and 4, +bind `second` to 8, then ignore 16 and 32, and so forth. The variable name +`second` doesn't mean anything special to Rust, so we get a compiler error +since using `..` in two places like this is ambiguous. + +### `ref` and `ref mut` to Create References in Patterns + +Usually, when you match against a pattern, the variables that the pattern +introduces are bound to a value. This means you'll end up moving the value into +the `match` (or wherever you're using the pattern) since the ownership rules +apply. Listing 18-24 shows an example: + +```rust,ignore +let robot_name = Some(String::from("Bors")); + +match robot_name { + Some(name) => println!("Found a name: {}", name), + None => (), +} + +println!("robot_name is: {:?}", robot_name); +``` + +Listing 18-24: Creating a variable in a match arm pattern +takes ownership of the value + +This example will fail to compile since the value inside the `Some` value in +`robot_name` is moved within the `match` when `name` binds to that value. + +Using `&` in a pattern matches an existing reference in the value, as we saw in +the "Destructuring to Break Apart Values" section. If you want to create a +reference instead in order to borrow the value in a pattern variable, use the +`ref` keyword before the new variable, as shown in Listing 18-25: + +```rust +let robot_name = Some(String::from("Bors")); + +match robot_name { + Some(ref name) => println!("Found a name: {}", name), + None => (), +} + +println!("robot_name is: {:?}", robot_name); +``` + +Listing 18-25: Creating a reference so that a pattern +variable does not take ownership of a value + +This example will compile because the value in the `Some` variant in +`robot_name` is not moved into the `Some(ref name)` arm of the match; the match +only took a reference to the data in `robot_name` rather than moving it. + +To create a mutable reference, use `ref mut` for the same reason as shown in +Listing 18-26: + +```rust +let mut robot_name = Some(String::from("Bors")); + +match robot_name { + Some(ref mut name) => *name = String::from("Another name"), + None => (), +} + +println!("robot_name is: {:?}", robot_name); +``` + +Listing 18-26: Creating a mutable reference to a value as +part of a pattern using `ref mut` + +This example will compile and print `robot_name is: Some("Another name")`. +Since `name` is a mutable reference, within the match arm code, we need to +dereference using the `*` operator in order to be able to mutate the value. + +### Extra Conditionals with Match Guards + +You can introduce *match guards* as part of a match arm by specifying an +additional `if` conditional after the pattern. The conditional can use +variables created in the pattern. Listing 18-27 has a `match` expression with a +match guard in the first arm: + +```rust +let num = Some(4); + +match num { + Some(x) if x < 5 => println!("less than five: {}", x), + Some(x) => println!("{}", x), + None => (), +} +``` + +Listing 18-27: Adding a match guard to a pattern + +This example will print `less than five: 4`. If `num` was instead `Some(7)`, +this example would print `7`. Match guards allow you to express more complexity +than patterns alone give you. + +In Listing 18-10, we saw that since patterns shadow variables, we weren't able +to specify a pattern to express the case when a value was equal to a variable +outside the `match`. Listing 18-28 shows how we can use a match guard to +accomplish this: + +```rust +fn main() { + let x = Some(5); + let y = 10; + + match x { + Some(50) => println!("Got 50"), + Some(n) if n == y => println!("Matched, n = {:?}", n), + _ => println!("Default case, x = {:?}", x), + } + + println!("at the end: x = {:?}, y = {:?}", x, y); +} +``` + +Listing 18-28: Using a match guard to test for equality +with an outer variable + +This will now print `Default case, x = Some(5)`. Because the second match arm +is not introducing a new variable `y` that shadows the outer `y` in the +pattern, we can use `y` in the match guard. We're still destructuring `x` to +get the inner value `n`, and then we can compare `n` and `y` in the match guard. + +If you're using a match guard with multiple patterns specified by `|`, the +match guard condition applies to all of the patterns. Listing 18-29 shows a +match guard that applies to the value matched by all three patterns in the +first arm: + +```rust +let x = 4; +let y = false; + +match x { + 4 | 5 | 6 if y => println!("yes"), + _ => println!("no"), +} +``` + +Listing 18-29: Combining multiple patterns with a match +guard + +This prints `no` since the `if` condition applies to the whole pattern `4 | 5 | +6`, not only to the last value `6`. In other words, the precedence of a match +guard in relation to a pattern behaves like this: + +```text +(4 | 5 | 6) if y => ... +``` + +rather than this: + +```text +4 | 5 | (6 if y) => ... +``` + +### `@` Bindings + +In order to test a value in a pattern but also be able to create a variable +bound to the value, we can use `@`. Listing 18-30 shows an example where we +want to test that a `Message::Hello` `id` field is within the range `3...7` but +also be able to bind to the value so that we can use it in the code associated +with the arm: + +```rust +enum Message { + Hello { id: i32 }, +} + +let msg = Message::Hello { id: 5 }; + +match msg { + Message::Hello { id: id @ 3...7 } => { + println!("Found an id in range: {}", id) + }, + Message::Hello { id: 10...12 } => { + println!("Found an id in another range") + }, + Message::Hello { id } => { + println!("Found some other id: {}", id) + }, +} +``` + +Listing 18-30: Using `@` to bind to a value in a pattern +while also testing it + +This example will print `Found an id in range: 5`. By specifying `id @` before +the range, we're capturing whatever value matched the range while also testing +it. In the second arm where we only have a range specified in the pattern, the +code associated with the arm doesn't know if `id` is 10, 11, or 12, since we +haven't saved the `id` value in a variable: we only know that the value matched +something in that range if that arm's code is executed. In the last arm where +we've specified a variable without a range, we do have the value available to +use in the arm's code, but we haven't applied any other test to the value. +Using `@` lets us test a value and save it in a variable within one pattern. + +## Summary + +Patterns are a useful feature of Rust that help to distinguish between +different kinds of data. When used in `match` statements, Rust makes sure that +your patterns cover every possible value. Patterns in `let` statements and +function parameters make those constructs more powerful, enabling the +destructuring of values into smaller parts at the same time as assigning to +variables. + +Now, for the penultimate chapter of the book, let's take a look at some +advanced parts of a variety of Rust's features. From 2a9357682aa479f2bacccba79cc930e998d180bb Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 26 Jun 2017 12:04:33 +0800 Subject: [PATCH 24/59] Update ch18-03-pattern-syntax.md --- src/ch18-03-pattern-syntax.md | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/ch18-03-pattern-syntax.md b/src/ch18-03-pattern-syntax.md index 795e221..8682ffe 100644 --- a/src/ch18-03-pattern-syntax.md +++ b/src/ch18-03-pattern-syntax.md @@ -1,12 +1,10 @@ -## All the Pattern Syntax +## 所有的模式语法 -We've seen some examples of different kinds of patterns throughout the book. -This section lists all the syntax valid in patterns and why you might want to -use each of them. +通过本书我们已领略过一些不同类型模式的例子. 本节会列出所有在模式中有效的语法并且会阐述你为什么可能会使用它们中的每一个. -### Literals +### 字面量 -As we saw in Chapter 6, you can match against literals directly: +我们在第6章已经见过, 你可以直接匹配字面量: ```rust let x = 1; @@ -19,19 +17,13 @@ match x { } ``` -This prints `one` since the value in `x` is 1. +这段代码会打印`one`因为`x`的值是1. -### Named Variables +### 命名变量 -Named variables are irrefutable patterns that match any value. +命名变量是可匹配任何值的`irrefutable`(不可反驳)模式. -As with all variables, variables declared as part of a pattern will shadow -variables with the same name outside of the `match` construct since a `match` -starts a new scope. In Listing 18-10, we declare a variable named `x` with the -value `Some(5)` and a variable `y` with the value `10`. Then we have a `match` -expression on the value `x`. Take a look at the patterns in the match arms and -the `println!` at the end, and make a guess about what will be printed before -running this code or reading further: +与所有变量一样, 模式中声明的变量会屏蔽`match`表达式外层的同名变量, 因为一个`match`表达式会开启一个新的作用域. 在列表18-10中, 我们声明了一个值为`Some(5)`的变量`x`和一个值为`10`的变量`y`. 然后是一个值`x`上的`match`表达式. 看一看开始匹配的模式和结尾的`println!`, 你可以在继续阅读或运行代码前猜一猜什么会被打印出来: Filename: src/main.rs @@ -50,12 +42,11 @@ fn main() { } ``` -Listing 18-10: A `match` statement with an arm that -introduces a shadowed variable `y` +列表18-10: 引入了一个阴影变量`y`的`match`语句 -Let's walk through what happens when the `match` statement runs. The first +让我们看看当`match`语句运行的时候发生了什么. The first match arm has the pattern `Some(50)`, and the value in `x` (`Some(5)`) does not match `Some(50)`, so we continue. In the second match arm, the pattern `Some(y)` introduces a new variable name `y` that will match any value inside a From 4aa76cc7e6f0871640233d8042ea09c1a863e607 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 26 Jun 2017 12:09:25 +0800 Subject: [PATCH 25/59] Update ch18-03-pattern-syntax.md --- src/ch18-03-pattern-syntax.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ch18-03-pattern-syntax.md b/src/ch18-03-pattern-syntax.md index 8682ffe..bceb680 100644 --- a/src/ch18-03-pattern-syntax.md +++ b/src/ch18-03-pattern-syntax.md @@ -23,7 +23,7 @@ match x { 命名变量是可匹配任何值的`irrefutable`(不可反驳)模式. -与所有变量一样, 模式中声明的变量会屏蔽`match`表达式外层的同名变量, 因为一个`match`表达式会开启一个新的作用域. 在列表18-10中, 我们声明了一个值为`Some(5)`的变量`x`和一个值为`10`的变量`y`. 然后是一个值`x`上的`match`表达式. 看一看开始匹配的模式和结尾的`println!`, 你可以在继续阅读或运行代码前猜一猜什么会被打印出来: +与所有变量一样, 模式中声明的变量会屏蔽`match`表达式外层的同名变量, 因为一个`match`表达式会开启一个新的作用域. 在列表18-10中, 我们声明了一个值为`Some(5)`的变量`x`和一个值为`10`的变量`y`. 然后是一个值`x`上的`match`表达式. 看一看匹配分支的模式和结尾的`println!`, 你可以在继续阅读或运行代码前猜一猜什么会被打印出来: Filename: src/main.rs @@ -46,9 +46,7 @@ fn main() { -让我们看看当`match`语句运行的时候发生了什么. The first -match arm has the pattern `Some(50)`, and the value in `x` (`Some(5)`) does not -match `Some(50)`, so we continue. In the second match arm, the pattern +让我们看看当`match`语句运行的时候发生了什么. 第一个匹配分支是模式`Some(50)`, `x`中的值(`Some(5)`)不匹配`Some(50)`, 所以我们继续. In the second match arm, the pattern `Some(y)` introduces a new variable name `y` that will match any value inside a `Some` value. Because we're in a new scope inside the `match` expression, this is a new variable, not the `y` we declared at the beginning that has the From a51305766cd662bf24229ea88f35b19965190c78 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 26 Jun 2017 12:10:57 +0800 Subject: [PATCH 26/59] Update ch18-03-pattern-syntax.md --- src/ch18-03-pattern-syntax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch18-03-pattern-syntax.md b/src/ch18-03-pattern-syntax.md index bceb680..166e11b 100644 --- a/src/ch18-03-pattern-syntax.md +++ b/src/ch18-03-pattern-syntax.md @@ -1,6 +1,6 @@ ## 所有的模式语法 -通过本书我们已领略过一些不同类型模式的例子. 本节会列出所有在模式中有效的语法并且会阐述你为什么可能会使用它们中的每一个. +通过本书我们已领略过一些不同类型模式的例子. 本节会列出所有在模式中有效的语法并且会阐述你为什么可能会用到它们中的每一个. ### 字面量 From 38693704bef90191b3740d3cf8f7ac84a9efedbb Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 26 Jun 2017 19:44:52 +0800 Subject: [PATCH 27/59] Update ch18-03-pattern-syntax.md --- src/ch18-03-pattern-syntax.md | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/ch18-03-pattern-syntax.md b/src/ch18-03-pattern-syntax.md index 166e11b..a442111 100644 --- a/src/ch18-03-pattern-syntax.md +++ b/src/ch18-03-pattern-syntax.md @@ -46,34 +46,18 @@ fn main() { -让我们看看当`match`语句运行的时候发生了什么. 第一个匹配分支是模式`Some(50)`, `x`中的值(`Some(5)`)不匹配`Some(50)`, 所以我们继续. In the second match arm, the pattern -`Some(y)` introduces a new variable name `y` that will match any value inside a -`Some` value. Because we're in a new scope inside the `match` expression, this -is a new variable, not the `y` we declared at the beginning that has the -value 10. The new `y` binding will match any value inside a `Some`, which is -what we have in `x`, so we execute the expression for that arm and print -`Matched, y = 5` since this `y` binds to the inner value of the `Some` in `x`, -which is 5. +让我们看看当`match`语句运行的时候发生了什么. 第一个匹配分支是模式`Some(50)`, `x`中的值(`Some(5)`)不匹配`Some(50)`, 所以我们继续. 在第二个匹配分支中, 模式`Some(y)`引入了一个可以匹配在`Some`里的任意值的新变量`y`. 因为我们位于`match`表达式里面的新作用域中, 所以`y`就是一个新变量而不是在开头被声明的其值为10的变量`y`. 这个新的`y`绑定将会匹配在`Some`中的任意值, 这里也就是`x`中的值, 因为`y`绑定到`Some`中的值是`x`, 这里是5, 所以我们就执行了这个分支中的表达式并打印出`Matched, y = 5`. -If `x` had been a `None` value instead of `Some(5)`, we would have matched the -underscore since the other two arms' patterns would not have matched. In the -expression for that match arm, since we did not introduce an `x` variable in -the pattern of the arm, this `x` is still the outer `x` that has not been -shadowed. In this hypothetical case, the `match` would print `Default case, x = +如果`x`的值是`None`而不是`Some(5)`, 我们将会匹配下划线因为其它两个分支的模式将不会被匹配. 在这个匹配分支(下划线)的表达式里, 因为我们没有在分支的模式中引入变量`x`, 所以这个`x`仍然是`match`作用域外部的那个没被屏蔽的`x`. 在这个假想的例子中, `match`表达式将会打印出`Default case, x = None`. -Once the `match` expression is over, its scope ends, and so does the scope of -the inner `y`. The last `println!` produces `at the end: x = Some(5), y = 10`. +一旦`match`表达式执行完毕, 它的作用域也就结束了, 同时`match`内部的`y`也就结束了. 最后的`println!`会打印`at the end: x = Some(5), y = 10`. -In order to make a `match` expression that compares the values of the outer `x` -and `y` rather than introducing a shadowed variable, we would need to use a -match guard conditional instead. We'll be talking about match guards later in -this section. +为了让`match`表达式能比较外部变量`x`和`y`的值而不是内部引入的阴影变量`x`和`y`, 我们需要使用一个有条件的匹配守卫(guard). 我们将在本节的后面讨论匹配守卫. -### Multiple patterns +### 多种模式 -In `match` expressions only, you can match multiple patterns with `|`, which -means *or*: +只有在`match`表达式中, 你可以通过`|`符号匹配多个模式, 它代表*或*(*or*)的意思: ```rust let x = 1; From 30016f120f3e04585868821d82434dd91ef4abae Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Tue, 27 Jun 2017 00:51:16 +0800 Subject: [PATCH 28/59] Update ch18-03-pattern-syntax.md --- src/ch18-03-pattern-syntax.md | 51 +++++++++++------------------------ 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/src/ch18-03-pattern-syntax.md b/src/ch18-03-pattern-syntax.md index a442111..134f96a 100644 --- a/src/ch18-03-pattern-syntax.md +++ b/src/ch18-03-pattern-syntax.md @@ -69,11 +69,11 @@ match x { } ``` -This prints `one or two`. +上面的代码会打印`one or two`. -### Matching Ranges of Values with `...` +### 通过`...`匹配值的范围 -You can match an inclusive range of values with `...`: +你可以用`...`匹配一个值包含的范围: ```rust let x = 5; @@ -84,10 +84,9 @@ match x { } ``` -If `x` is 1, 2, 3, 4, or 5, the first arm will match. +上面的代码中, 如果`x`是1、 2、 3、 4或5, 第一个分支就会匹配. -Ranges are only allowed with numeric values or `char` values. Here's an example -using ranges of `char` values: +范围只能是数字或`char`类型的值. 下面是一个使用`char`类型值范围的例子: ```rust let x = 'c'; @@ -99,14 +98,11 @@ match x { } ``` -This will print `early ASCII letter`. +上面的代码会打印`early ASCII letter`. -### Destructuring to Break Apart Values +### 解构提取值 -Patterns can be used to *destructure* structs, enums, tuples, and references. -Destructuring means to break a value up into its component pieces. Listing -18-11 shows a `Point` struct with two fields, `x` and `y`, that we can break -apart by using a pattern with a `let` statement: +模式可以用来*解构*(*destructure*)结构、枚举、元组和引用. 解构意味着把一个值分解成它的组成部分. 例18-11中的结构`Point`有两个字段`x`和`y`, 我们可以通过一个模式和`let`语句来进行提取: Filename: src/main.rs @@ -125,15 +121,9 @@ fn main() { } ``` -Listing 18-11: Destructuring using struct field -shorthand +例18-11: 用结构的字段来解构 -This creates the variables `x` and `y` that match the `x` and `y` of `p`. The -names of the variables must match the names of the fields to use this -shorthand. If we wanted to use names different than the variable names, we can -specify `field_name: variable_name` in the pattern. In Listing 18-12, `a` will -have the value in the `Point` instance's `x` field and `b` will have the value -in the `y` field: +上面的代码创建了匹配`p`中的`x`和`y`字段的变量`x`和`y`. 变量的名字必须匹配使用了这个写法中的字段. 如果我们想使用不同的变量名字, 我们可以在模式中使用`field_name: variable_name`. 在例18-12中, `a`会拥有`Point`实例的`x`字段的值, `b`会拥有`y`字段的值: Filename: src/main.rs @@ -152,13 +142,9 @@ fn main() { } ``` -Listing 18-12: Destructuring struct fields into variables -with different names than the fields +例18-12: 把结构解构到与字段不同名的变量中 -We can also use destructuring with literal values in order to test and use -inner parts of a value. Listing 18-13 shows a `match` statement that determines -whether a point lies directly on the `x` axis (which is true when `y = 0`), on -the `y` axis (`x = 0`), or neither: +为了测试和使用一个值内部的某个属性, 我们也可以用字面量来解构. 例18-13用一个`match`语句来判断一个点是位于`x`(此时`y` = 0)轴上还是在`y`(此时`x` = 0)轴上或者不在两个轴上面: ```rust # struct Point { @@ -177,18 +163,13 @@ fn main() { } ``` -Listing 18-13: Destructuring and matching literal values -in one pattern +例18-13: 解构和匹配一个模式中的字面量 -This will print `On the y axis at 7` since the value `p` matches the second arm -by virtue of `x` having the value 0. +上面的代码会打印`On the y axis at 7`, 因为`p`的`x`字段的值是0, 这正好匹配第二个分支. -We used destructuring on enums in Chapter 6, such as in Listing 6-5 where we -destructured an `Option` using a `match` expression and added one to the -inner value of the `Some` variant. +在第6章中我们对枚举进行了解构, 比如例6-5中, 我们用一个`match`表达式来解构一个`Option`, 其中被提取出来的一个是`Some`内的变量. -When the value we're matching against a pattern contains a reference, we can -specify a `&` in the pattern in order to separate the reference and the value. +当我们正匹配的值在一个包含了引用的模式里面时, 为了把引用和值分割开我们可以在模式中指定一个`&`符号. This is especially useful in closures used with iterators that iterate over references to values when we want to use the values in the closure rather than the references. Listing 18-14 shows how to iterate over references to `Point` From fe438ab055797dbfd4c80e02e1d5855b3e0adc4b Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Tue, 27 Jun 2017 00:53:44 +0800 Subject: [PATCH 29/59] Update ch18-03-pattern-syntax.md --- src/ch18-03-pattern-syntax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch18-03-pattern-syntax.md b/src/ch18-03-pattern-syntax.md index 134f96a..accb5df 100644 --- a/src/ch18-03-pattern-syntax.md +++ b/src/ch18-03-pattern-syntax.md @@ -167,7 +167,7 @@ fn main() { 上面的代码会打印`On the y axis at 7`, 因为`p`的`x`字段的值是0, 这正好匹配第二个分支. -在第6章中我们对枚举进行了解构, 比如例6-5中, 我们用一个`match`表达式来解构一个`Option`, 其中被提取出来的一个是`Some`内的变量. +在第6章中我们对枚举进行了解构, 比如例6-5中, 我们用一个`match`表达式来解构一个`Option`, 其中被提取出来的一个值是`Some`内的变量. 当我们正匹配的值在一个包含了引用的模式里面时, 为了把引用和值分割开我们可以在模式中指定一个`&`符号. This is especially useful in closures used with iterators that iterate over From 4bb6a2dc43f812a55242d89399f1c73b39c175af Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Tue, 27 Jun 2017 12:02:59 +0800 Subject: [PATCH 30/59] Update ch18-03-pattern-syntax.md --- src/ch18-03-pattern-syntax.md | 81 ++++++++++------------------------- 1 file changed, 22 insertions(+), 59 deletions(-) diff --git a/src/ch18-03-pattern-syntax.md b/src/ch18-03-pattern-syntax.md index accb5df..cd88878 100644 --- a/src/ch18-03-pattern-syntax.md +++ b/src/ch18-03-pattern-syntax.md @@ -169,12 +169,7 @@ fn main() { 在第6章中我们对枚举进行了解构, 比如例6-5中, 我们用一个`match`表达式来解构一个`Option`, 其中被提取出来的一个值是`Some`内的变量. -当我们正匹配的值在一个包含了引用的模式里面时, 为了把引用和值分割开我们可以在模式中指定一个`&`符号. -This is especially useful in closures used with iterators that iterate over -references to values when we want to use the values in the closure rather than -the references. Listing 18-14 shows how to iterate over references to `Point` -instances in a vector, and destructure both the reference and the struct in -order to be able to perform calculations on the `x` and `y` values easily: +当我们正匹配的值在一个包含了引用的模式里面时, 为了把引用和值分割开我们可以在模式中指定一个`&`符号. 在迭代器对值的引用进行迭代时当我们想在闭包中使用值而不是引用的时侯这个符号在闭包里特别有用. 例18-14演示了如何在一个向量里迭代`Point`实例的引用, 为了能方便地对`x`和`y`的值进行计算还对引用的结构进行了解构: ```rust # struct Point { @@ -193,12 +188,9 @@ let sum_of_squares: i32 = points .sum(); ``` -Listing 18-14: Destructuring a reference to a struct into -the struct field values +例18-14: 把结构的引用解构到结构的字段值中 -Because `iter` iterates over references to the items in the vector, if we -forgot the `&` in the closure arguments in the `map`, we'd get a type mismatch -error like this: +因为`iter`会对向量里面的项目的引用进行迭代, 如果我们在`map`里的闭包的参数上忘了`&`符号, 我们将会得到下面的类型不匹配的错误: ```text error[E0308]: mismatched types @@ -211,13 +203,9 @@ error[E0308]: mismatched types found type `Point` ``` -This says Rust was expecting our closure to match `&Point`, but we tried to -match the value with a pattern that was a `Point` value, not a reference to a -`Point`. +这个报错提示Rust希望我们的闭包匹配参数匹配`&Point`, 但是我们却试图用一个`Point`的值的模式去匹配它, 而不是一个`Point`的引用. -We can mix, match, and nest destructuring patterns in even more complex ways: -we can do something complicated like this example where we nest structs and -tuples inside of a tuple and destructure all the primitive values out: +我们可以用更复杂的方法来合成、匹配和嵌套解构模式: 下例中我们通过在一个元组中嵌套结构和元组来解构出所有的基础类型的值: ```rust # struct Point { @@ -228,21 +216,15 @@ tuples inside of a tuple and destructure all the primitive values out: let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 }); ``` -This lets us break complex types into their component parts. +这使得我们把复杂的类型提取成了它们的组成成分. -### Ignoring Values in a Pattern +### 忽略模式中的值 -There are a few ways to ignore entire values or parts of values: using the `_` -pattern, using the `_` pattern within another pattern, using a name that starts -with an underscore, or using `..` to ignore all remaining parts of a value. -Let's explore how and why to do each of these. +有一些简单的方法可以忽略模式中全部或部分值: 使用`_`模式, 在另一个模式中使用`_`模式, 使用一个以下划线开始的名字, 或者使用`..`来忽略掉所有剩下的值. 下面让我们来探索如何以及为什么要这么做. -#### Ignoring an Entire Value with `_` +#### 用`_`忽略整个值 -We've seen the use of underscore as a wildcard pattern that will match any value -but not bind to the value. While the underscore pattern is especially useful as -the last arm in a `match` expression, we can use it in any pattern, such as -function arguments as shown in Listing 18-15: +我们已经见过了用下划线作为通配符会匹配任意值, 但是它不会绑定值. 把下划线模式用作`match`表达式的最后一个匹配分支特别有用, 我们可以在任意模式中使用它, 比如在例18-15中显示的函数参数: ```rust fn foo(_: i32) { @@ -250,18 +232,13 @@ fn foo(_: i32) { } ``` -Listing 18-15: Using `_` in a function signature +例18-15: 在一个函数签名中使用`_` -Normally, you would change the signature to not have the unused parameter. In -cases such as implementing a trait, where you need a certain type signature, -using an underscore lets you ignore a parameter, and the compiler won't warn -about unused function parameters like it would if we had used a name instead. +通常, 你应该把这种函数的参数声明改成不用无用参数. 如果是要实现这样一个有特定类型签名的*trait*, 使用下划线可以让你忽略一个参数, 并且编译器不会像使用命名参数那样警告有未使用的函数参数. -#### Ignoring Parts of a Value with a Nested `_` +#### 用一个嵌套的`_`忽略部分值 -We can also use `_` inside of another pattern to ignore just part of a value. -In Listing 18-16, the first `match` arm's pattern matches a `Some` value but -ignores the value inside of the `Some` variant as specified by the underscore: +我们也可以在另一个模式中使用`_`来忽略部分值. 在例18-16中, 第一个`match`分支中的模式匹配了一个`Some`值, 但是却通过下划线忽略掉了`Some`变量中的值: ```rust let x = Some(5); @@ -272,15 +249,11 @@ match x { } ``` -Listing 18-16: Ignoring the value inside of the `Some` -variant by using a nested underscore +例18-16: 通过使用一个嵌套的下划线忽略`Some`变量中的值 -This is useful when the code associated with the `match` arm doesn't use the -nested part of the variable at all. +当代码关联的`match`分支不需要使用被嵌套的全部变量时这很有用. -We can also use underscores in multiple places within one pattern, as shown in -Listing 18-17 where we're ignoring the second and fourth values in a tuple of -five items: +我们也可以在一个模式中多处使用下划线, 在例18-17中我们将忽略掉一个五元元组中的第二和第四个值: ```rust let numbers = (2, 4, 8, 16, 32); @@ -292,22 +265,13 @@ match numbers { } ``` -Listing 18-17: Ignoring multiple parts of a tuple +例18-17: 忽略元组中的多个部分 -This will print `Some numbers: 2, 8, 32`, and the values 4 and 16 will be -ignored. +上面的代码将会打印出`Some numbers: 2, 8, 32`, 元组中的4和16会被忽略. -#### Ignoring an Unused Variable by Starting its Name with an Underscore +#### 通过在名字前以一个下划线开头来忽略不使用的变量 -Usually, Rust will warn you if you create a variable but don't use it anywhere, -since that could be a bug. If you're prototyping or just starting a project, -though, you might create a variable that you'll use eventually, but temporarily -it will be unused. If you're in this situation and would like to tell Rust not -to warn you about the unused variable, you can start the name of the variable -with an underscore. This works just like a variable name in any pattern, only -Rust won't warn you if the variable goes unused. In Listing 18-18, we -do get a warning about not using the variable `y`, but we don't get a warning -about not using the variable `_x`: +如果你创建了一个变量却不使用它, Rust通常会给你一个警告, 因为这可能会是个bug. 如果你正在做原型或者刚开启一个项目, 那么你可能会创建一个暂时不用但是以后会使用的变量. 如果你面临这个情况并且希望Rust不要对你警告未使用的变量, 你可以让那个变量以一个下划线开头. 这和其它模式中的变量名没什么区别, 只是Rust不会警告你这个变量没用被使用. 在例18-18中, 我们会得到一个没用使用变量`y`的警告, 但是我们不会得到没用使用变量`_x`的警告: ```rust fn main() { @@ -316,8 +280,7 @@ fn main() { } ``` -Listing 18-18: Starting a variable name with an underscore -in order to not get unused variable warnings +例18-18: 为了消除对未被使用变量的警告以一个下划线开始来命名变量 Note that there is a subtle difference between using only `_` and using a name that starts with an underscore like `_x`: `_x` still binds the value to the From a451a0e824c57526e6bdf3db5257382ca3c57801 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Tue, 27 Jun 2017 18:06:09 +0800 Subject: [PATCH 31/59] Update ch18-03-pattern-syntax.md --- src/ch18-03-pattern-syntax.md | 72 ++++++++++------------------------- 1 file changed, 21 insertions(+), 51 deletions(-) diff --git a/src/ch18-03-pattern-syntax.md b/src/ch18-03-pattern-syntax.md index cd88878..ace0508 100644 --- a/src/ch18-03-pattern-syntax.md +++ b/src/ch18-03-pattern-syntax.md @@ -282,12 +282,9 @@ fn main() { 例18-18: 为了消除对未被使用变量的警告以一个下划线开始来命名变量 -Note that there is a subtle difference between using only `_` and using a name -that starts with an underscore like `_x`: `_x` still binds the value to the -variable, but `_` doesn't bind at all. +注意, 只使用`_`和使用一个以一个下划线起头的名字是有微妙的不同的: `_x`仍然会把值绑定到变量上但是`_`不会绑定值. -Listing 18-19 shows a case where this distinction matters: `s` will still be -moved into `_s`, which prevents us from using `s` again: +例18-19显示了这种区别的主要地方: `s`将仍然被转移到`_s`, 它会阻止我们继续使用`s`: ```rust,ignore let s = Some(String::from("Hello!")); @@ -299,11 +296,9 @@ if let Some(_s) = s { println!("{:?}", s); ``` -Listing 18-19: An unused variable starting with an -underscore still binds the value, which may take ownership of the value +例18-19: 以下划线起头的未被使用的变量仍然会绑定值, 它也会拥有值的所有权 -Using underscore by itself, however, doesn't ever bind to the value. Listing -18-20 will compile without any errors since `s` does not get moved into `_`: +只使用下划线本身却不会绑定值. 例18-20在编译时将不会报错, 因为`s`不会被转移到`_`: ```rust let s = Some(String::from("Hello!")); @@ -315,20 +310,14 @@ if let Some(_) = s { println!("{:?}", s); ``` -Listing 18-20: Using underscore does not bind the -value +例18-20: 使用下划线不会绑定值 -This works just fine. Because we never bind `s` to anything, it's not moved. +上面的代码能很好的运行. 因为我们没有把`s`绑定到其它地方, 它没有被转移. -#### Ignoring Remaining Parts of a Value with `..` +#### 用`..`忽略剩余的值 -With values that have many parts, we can extract only a few parts and avoid -having to list underscores for each remaining part by instead using `..`. The -`..` pattern will ignore any parts of a value that we haven't explicitly -matched in the rest of the pattern. In Listing 18-21, we have a `Point` struct -that holds a coordinate in three dimensional space. In the `match` expression, -we only want to operate on the `x` coordinate and ignore the values in the `y` -and `z` fields: +对于有多个字段的值而言, 我们可以只提取少数字段并使用`..`来代替下划线, 这就避免了用`_`把剩余的部分列出来的麻烦. `..`模式将忽略值中没有被精确匹配值中的其它部分. 在例18-21中, 我们有一个持有三维空间坐标的`Point`结构. 在`match`表达式里, +我们只想操作`x`坐标上的值并忽略`y`坐标和`z`坐标上的值: ```rust struct Point { @@ -344,15 +333,11 @@ match origin { } ``` -Listing 18-21: Ignoring all fields of a `Point` except -for `x` by using `..` +例18-21: 通过用`..`来忽略除了`x`以外的所有其它`Point`的字段 -Using `..` is shorter to type than having to list out `y: _` and `z: _`. The -`..` pattern is especially useful when working with structs that have lots of -fields in situations where only one or two fields are relevant. +使用`..`比列出`y: _`和`z: _`写起来更简单. 当一个结构有很多字段但却只需要使用少量字段时`..`模式就特别有用. -`..` will expand to as many values as it needs to be. Listing 18-22 shows a use -of `..` with a tuple: +`..`将会囊括它能匹配的尽可能多的值. 例18-22显示了一个在元组中使用`..`的情况: ```rust fn main() { @@ -366,15 +351,11 @@ fn main() { } ``` -Listing 18-22: Matching only the first and last values in -a tuple and ignoring all other values with `..` +例18-22: 用`..`匹配元组中的第一和最后一个值并忽略掉所有的其它值 -Here, we have the first and last value matched, with `first` and `last`. The -`..` will match and ignore all of the things in the middle. +我们在这里用`first`和`last`来匹配了第一和最后一个值. `..`将匹配并忽略中间的所有其它值. -Using `..` must be unambiguous, however. Listing 18-23 shows an example where -it's not clear to Rust which values we want to match and which values we want -to ignore: +然而使用`..`必须清晰明了. 例18-23中的代码就不是很清晰, Rust看不出哪些值时我们想匹配的, 也看不出哪些值是我们想忽略的: ```rust,ignore fn main() { @@ -388,10 +369,9 @@ fn main() { } ``` -Listing 18-23: An attempt to use `..` in a way that is -ambiguous +例18-23: 尝试含混不清地使用`..` -If we compile this example, we get this error: +如果我们编译上面的例子, 我们会得到下面的错误: ```text error: `..` can only be used once per tuple or tuple struct pattern @@ -401,20 +381,11 @@ error: `..` can only be used once per tuple or tuple struct pattern | ^^ ``` -It's not possible to determine how many values in the tuple should be ignored -before one value is matched with `second`, and then how many further values are -ignored after that. We could mean that we want to ignore 2, bind `second` to 4, -then ignore 8, 16, and 32, or we could mean that we want to ignore 2 and 4, -bind `second` to 8, then ignore 16 and 32, and so forth. The variable name -`second` doesn't mean anything special to Rust, so we get a compiler error -since using `..` in two places like this is ambiguous. +上面的代码中在一个值被匹配到`second`之前不可能知道元组中有多少值应该被忽略, 同样在`second`被匹配后也不知道应该有多少值被忽略. 我们可以忽略2, 把`second`绑定到4, 然后忽略8、16和32, 或者我们也可以忽略2和4, 把`second`绑定到8, 然后再忽略16和32. 对Rust而言, 变量名`second`并不意味着某个确定的值, 因为像这样在两个地方使用`..`是含混不清的, 所以我们就得到了一个编译错误. -### `ref` and `ref mut` to Create References in Patterns +### 用`ref`和`ref mut`在模式中创建引用 -Usually, when you match against a pattern, the variables that the pattern -introduces are bound to a value. This means you'll end up moving the value into -the `match` (or wherever you're using the pattern) since the ownership rules -apply. Listing 18-24 shows an example: +当你匹配一个模式时, 模式匹配的变量会被绑定到一个值. 也就是说你会把值转移进`match`(或者是其它你使用了模式的地方), 这是所有权规则的作用. 例18-24提供了一个例子: ```rust,ignore let robot_name = Some(String::from("Bors")); @@ -427,8 +398,7 @@ match robot_name { println!("robot_name is: {:?}", robot_name); ``` -Listing 18-24: Creating a variable in a match arm pattern -takes ownership of the value +例18-24: 在一个匹配分支模式里创建的变量会拥有值的所有权 This example will fail to compile since the value inside the `Some` value in `robot_name` is moved within the `match` when `name` binds to that value. From ee497ca372277e26edb9c5414103eab607458ef9 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Tue, 27 Jun 2017 19:42:26 +0800 Subject: [PATCH 32/59] Update ch18-03-pattern-syntax.md --- src/ch18-03-pattern-syntax.md | 53 ++++++++++------------------------- 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/src/ch18-03-pattern-syntax.md b/src/ch18-03-pattern-syntax.md index ace0508..d43c822 100644 --- a/src/ch18-03-pattern-syntax.md +++ b/src/ch18-03-pattern-syntax.md @@ -100,7 +100,7 @@ match x { 上面的代码会打印`early ASCII letter`. -### 解构提取值 +### 解构并提取值 模式可以用来*解构*(*destructure*)结构、枚举、元组和引用. 解构意味着把一个值分解成它的组成部分. 例18-11中的结构`Point`有两个字段`x`和`y`, 我们可以通过一个模式和`let`语句来进行提取: @@ -400,13 +400,9 @@ println!("robot_name is: {:?}", robot_name); 例18-24: 在一个匹配分支模式里创建的变量会拥有值的所有权 -This example will fail to compile since the value inside the `Some` value in -`robot_name` is moved within the `match` when `name` binds to that value. +上例的代码不能编译通过, 因为`robot_name`中的值被转移到了`match`中的`Some`的值所绑定的`name`里了. -Using `&` in a pattern matches an existing reference in the value, as we saw in -the "Destructuring to Break Apart Values" section. If you want to create a -reference instead in order to borrow the value in a pattern variable, use the -`ref` keyword before the new variable, as shown in Listing 18-25: +在模式中使用`&`会匹配已存在的引用中的值, 我们在"解构并提取值"这一节中已经见过了. 如果你想创建一个引用来借用模式中变量的值, 可以在新变量名前使用`ref`关键字, 比如例18-25: ```rust let robot_name = Some(String::from("Bors")); @@ -419,15 +415,11 @@ match robot_name { println!("robot_name is: {:?}", robot_name); ``` -Listing 18-25: Creating a reference so that a pattern -variable does not take ownership of a value +例18-25: 创建一个引用这样模式中的变量就不会拥有值的所有权 -This example will compile because the value in the `Some` variant in -`robot_name` is not moved into the `Some(ref name)` arm of the match; the match -only took a reference to the data in `robot_name` rather than moving it. +上例可以编译, 因为`robot_name`没有被转移到`Some(ref name)`匹配分支的`Some`变量中; 这个匹配分支只是持有`robot_name`中的数据, `robot_name`并没被转移. -To create a mutable reference, use `ref mut` for the same reason as shown in -Listing 18-26: +如果要创建一个可变引用, 可以像例18-26那样使用`ref mut`: ```rust let mut robot_name = Some(String::from("Bors")); @@ -440,19 +432,13 @@ match robot_name { println!("robot_name is: {:?}", robot_name); ``` -Listing 18-26: Creating a mutable reference to a value as -part of a pattern using `ref mut` +例18-26: 在模式中使用`ref mut`来创建一个值的可变引用 -This example will compile and print `robot_name is: Some("Another name")`. -Since `name` is a mutable reference, within the match arm code, we need to -dereference using the `*` operator in order to be able to mutate the value. +上例可以编译并打印出`robot_name is: Some("Another name")`. 因为在匹配分支的代码中`name`是一个可变引用, 为了能够改变这个值, 我们需要用`*`操作符来对它解引用. -### Extra Conditionals with Match Guards +### 用了匹配守卫的额外条件 -You can introduce *match guards* as part of a match arm by specifying an -additional `if` conditional after the pattern. The conditional can use -variables created in the pattern. Listing 18-27 has a `match` expression with a -match guard in the first arm: +你可以通过在模式后面指定一个额外的`if`条件来往匹配分支中引入*匹配守卫*(*match guards*). 这个条件可以使用模式中创建的变量. 例18-27中的`match`表达式的第一个匹配分支就有一个匹配守卫: ```rust let num = Some(4); @@ -464,16 +450,11 @@ match num { } ``` -Listing 18-27: Adding a match guard to a pattern +例18-27: 往一个模式中加入匹配守卫 -This example will print `less than five: 4`. If `num` was instead `Some(7)`, -this example would print `7`. Match guards allow you to express more complexity -than patterns alone give you. +上例会打印`less than five: 4`. 如果把`num`换成`Some(7)`, 上例将会打印`7`. 匹配守卫让你能表达出模式不能给予你的更多的复杂的东西. -In Listing 18-10, we saw that since patterns shadow variables, we weren't able -to specify a pattern to express the case when a value was equal to a variable -outside the `match`. Listing 18-28 shows how we can use a match guard to -accomplish this: +在例18-10中, 我们见过了模式中的阴影变量, 当一个值等于`match`外部的变量时我们不能用模式来表达出这种情况. 例18-28演示了我们如何用一个匹配守卫来解决这个问题: ```rust fn main() { @@ -490,13 +471,9 @@ fn main() { } ``` -Listing 18-28: Using a match guard to test for equality -with an outer variable +例18-28: 用一个匹配守卫来测试与外部变量的相等性 -This will now print `Default case, x = Some(5)`. Because the second match arm -is not introducing a new variable `y` that shadows the outer `y` in the -pattern, we can use `y` in the match guard. We're still destructuring `x` to -get the inner value `n`, and then we can compare `n` and `y` in the match guard. +上例会打印出`Default case, x = Some(5)`. 因为第二个匹配分支没有往模式中引入新变量`y`, 所以外部变量`y`就不会被遮掩, 这样我们就可以在匹配守卫中直接使用外部变量`y`. 我们还把`x`解构到了内部变量`n`中, 这样我们就可以在匹配守卫中比较`n`和`y`了. If you're using a match guard with multiple patterns specified by `|`, the match guard condition applies to all of the patterns. Listing 18-29 shows a From 9cd206f2358e6d5f3684f21012882ade29fcf1c7 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 28 Jun 2017 00:03:29 +0800 Subject: [PATCH 33/59] Update ch18-03-pattern-syntax.md --- src/ch18-03-pattern-syntax.md | 48 +++++++++-------------------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/src/ch18-03-pattern-syntax.md b/src/ch18-03-pattern-syntax.md index d43c822..aa1d19f 100644 --- a/src/ch18-03-pattern-syntax.md +++ b/src/ch18-03-pattern-syntax.md @@ -475,10 +475,7 @@ fn main() { 上例会打印出`Default case, x = Some(5)`. 因为第二个匹配分支没有往模式中引入新变量`y`, 所以外部变量`y`就不会被遮掩, 这样我们就可以在匹配守卫中直接使用外部变量`y`. 我们还把`x`解构到了内部变量`n`中, 这样我们就可以在匹配守卫中比较`n`和`y`了. -If you're using a match guard with multiple patterns specified by `|`, the -match guard condition applies to all of the patterns. Listing 18-29 shows a -match guard that applies to the value matched by all three patterns in the -first arm: +如果你在由`|`指定的多模式中使用匹配守卫, 匹配守卫的条件就会应用到所有的模式上. 例18-29演示了在第一个匹配分支中的匹配守卫会在被匹配的全部三个模式的值上生效: ```rust let x = 4; @@ -490,30 +487,24 @@ match x { } ``` -Listing 18-29: Combining multiple patterns with a match -guard +例18-29: 用一个匹配守卫来合成多个模式 -This prints `no` since the `if` condition applies to the whole pattern `4 | 5 | -6`, not only to the last value `6`. In other words, the precedence of a match -guard in relation to a pattern behaves like this: +上例会打印`no`因为条件`if`会应用到整个模式`4 | 5 | +6`上, 而不是只应用到最后一个值`6`上面. 换一种说法, 一个与模式关联的匹配守卫的优先级是: ```text (4 | 5 | 6) if y => ... ``` -rather than this: +而不是: ```text 4 | 5 | (6 if y) => ... ``` -### `@` Bindings +### `@`绑定 -In order to test a value in a pattern but also be able to create a variable -bound to the value, we can use `@`. Listing 18-30 shows an example where we -want to test that a `Message::Hello` `id` field is within the range `3...7` but -also be able to bind to the value so that we can use it in the code associated -with the arm: +为了既能测试一个模式的值又能创建一个绑定到值的变量, 我们可以使用`@`. 例18-30演示了在匹配分支中我们想测试一个`Message::Hello`的`id`字段是否位于`3...7`之间, 同时我们又想绑定这个值这样我们可以在代码中使用它: ```rust enum Message { @@ -535,27 +526,12 @@ match msg { } ``` -Listing 18-30: Using `@` to bind to a value in a pattern -while also testing it +例18-30: 在测试模式中的值的时候用`@`符号来绑定值 -This example will print `Found an id in range: 5`. By specifying `id @` before -the range, we're capturing whatever value matched the range while also testing -it. In the second arm where we only have a range specified in the pattern, the -code associated with the arm doesn't know if `id` is 10, 11, or 12, since we -haven't saved the `id` value in a variable: we only know that the value matched -something in that range if that arm's code is executed. In the last arm where -we've specified a variable without a range, we do have the value available to -use in the arm's code, but we haven't applied any other test to the value. -Using `@` lets us test a value and save it in a variable within one pattern. +上例会打印`Found an id in range: 5`. 通过在范围前指定`id @`, 我们就在测试模式的同时又捕获了匹配范围的值. 在第二个分支我们只有一个在模式中指定的范围, 与这个分支关联的代码就不知道`id`是10还是11或12, 因为我们没有把`id`的值保存在某个变量中: 我们只知道如果匹配分支代码被执行这个值与范围匹配. 在最后一个匹配分支中我们指定了一个无范围的变量, 这个值就可以用在分支代码中, 此时我们没有对这个值进行任何其它的测试. 在一个模式中使用`@`让我们可以测试模式中的值并把它保存在一个变量中. -## Summary +## 总结 -Patterns are a useful feature of Rust that help to distinguish between -different kinds of data. When used in `match` statements, Rust makes sure that -your patterns cover every possible value. Patterns in `let` statements and -function parameters make those constructs more powerful, enabling the -destructuring of values into smaller parts at the same time as assigning to -variables. +模式是Rust的一个很有用的特点, 它帮助区分不同类型的数据. 当被用在`match`语句中时, Rust确保你的模式覆盖了每个可能的值. 在`let`语句和函数参数中的模式使得这些构造更加强大, 这些模式在赋值给变量的同时可以把值解构成更小的部分. -Now, for the penultimate chapter of the book, let's take a look at some -advanced parts of a variety of Rust's features. +现在让我们进入倒数第二章吧, 让我们看一下Rust的某些高级特性. From ebbe7f517c18d82d7b389baa05e5757422b2b7d9 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 28 Jun 2017 00:18:19 +0800 Subject: [PATCH 34/59] Create ch19-00-advanced-features.md --- src/ch19-00-advanced-features.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/ch19-00-advanced-features.md diff --git a/src/ch19-00-advanced-features.md b/src/ch19-00-advanced-features.md new file mode 100644 index 0000000..fc9294b --- /dev/null +++ b/src/ch19-00-advanced-features.md @@ -0,0 +1,23 @@ +# Advanced Features + +We've come a long way! By now, we've learned 99% of the things you'll need to +know when writing Rust. Before we do one more project in Chapter 20, let's talk +about a few things that you may run into that last 1% of the time. Feel free to +skip this chapter and come back to it once you run into these things in the +wild; the features we'll learn to use here are useful in very specific +situations. We don't want to leave these features out, but you won't find +yourself reaching for them often. + +In this chapter, we're going to cover: + +* Unsafe Rust: for when you need to opt out of some of Rust's guarantees and + tell the compiler that you will be responsible for upholding the guarantees + instead +* Advanced Lifetimes: Additional lifetime syntax for complex situations +* Advanced Traits: Associated Types, default type parameters, fully qualified + syntax, supertraits, and the newtype pattern in relation to traits +* Advanced Types: some more about the newtype pattern, type aliases, the + "never" type, and dynamically sized types +* Advanced Functions and Closures: function pointers and returning closures + +It's a panoply of Rust features with something for everyone! Let's dive in! From 5f0e99592406c481c92cb0c2fcd02d6fde3395fd Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 28 Jun 2017 00:43:29 +0800 Subject: [PATCH 35/59] Update ch19-00-advanced-features.md --- src/ch19-00-advanced-features.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/ch19-00-advanced-features.md b/src/ch19-00-advanced-features.md index fc9294b..678cc7a 100644 --- a/src/ch19-00-advanced-features.md +++ b/src/ch19-00-advanced-features.md @@ -1,14 +1,8 @@ -# Advanced Features +# 高级特性 -We've come a long way! By now, we've learned 99% of the things you'll need to -know when writing Rust. Before we do one more project in Chapter 20, let's talk -about a few things that you may run into that last 1% of the time. Feel free to -skip this chapter and come back to it once you run into these things in the -wild; the features we'll learn to use here are useful in very specific -situations. We don't want to leave these features out, but you won't find -yourself reaching for them often. +我们已经走了很长的路! 现在我们已经学了使用Rust时99%的需要学习的内容. 在我们做第20章中的项目之前, 让我们来谈谈你可能会遇到的最后的1%的问题. 你可以随便跳过本章, 当你在实作中遇到这些问题时再回过头来学习也无妨; 我们将学习的在这里列出的特性在某些特定的情况下非常有用. 我们不想舍弃这些特性, 但你用到它们的时候确实不多. -In this chapter, we're going to cover: +本章将覆盖如下内容: * Unsafe Rust: for when you need to opt out of some of Rust's guarantees and tell the compiler that you will be responsible for upholding the guarantees From 1c147ee485041c106c33f6a1bd34a5b45877c38e Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 28 Jun 2017 11:39:42 +0800 Subject: [PATCH 36/59] Update ch19-00-advanced-features.md --- src/ch19-00-advanced-features.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/ch19-00-advanced-features.md b/src/ch19-00-advanced-features.md index 678cc7a..daf2787 100644 --- a/src/ch19-00-advanced-features.md +++ b/src/ch19-00-advanced-features.md @@ -1,17 +1,14 @@ # 高级特性 -我们已经走了很长的路! 现在我们已经学了使用Rust时99%的需要学习的内容. 在我们做第20章中的项目之前, 让我们来谈谈你可能会遇到的最后的1%的问题. 你可以随便跳过本章, 当你在实作中遇到这些问题时再回过头来学习也无妨; 我们将学习的在这里列出的特性在某些特定的情况下非常有用. 我们不想舍弃这些特性, 但你用到它们的时候确实不多. +我们已经走得很远了! 现在我们已经学了使用Rust时99%的需要学习的内容. 在我们做第20章中的项目之前, 让我们来谈谈你可能会遇到的最后的1%的问题. 你可以随便跳过本章, 当你在实作中遇到这些问题时再回过头来学习也无妨; 我们将学习的在这里列出的特性在某些特定的情况下非常有用. 我们不想舍弃这些特性, 但你用到它们的时候确实不多. 本章将覆盖如下内容: -* Unsafe Rust: for when you need to opt out of some of Rust's guarantees and - tell the compiler that you will be responsible for upholding the guarantees - instead -* Advanced Lifetimes: Additional lifetime syntax for complex situations -* Advanced Traits: Associated Types, default type parameters, fully qualified - syntax, supertraits, and the newtype pattern in relation to traits -* Advanced Types: some more about the newtype pattern, type aliases, the - "never" type, and dynamically sized types -* Advanced Functions and Closures: function pointers and returning closures +* Unsafe Rust: 用于当你需要弃用Rust的某些保证时你告诉编译器你将负责维护某些保证 +* 高级生命周期: 应对复杂情况的额外的生命周期语法 +* 高级Traits: 关联类型, 默认类型参数, 完全合格的句法(fully qualified + syntax), 超级trait(supertraits), 和与trait关联的新类型模式 +* 高级类型: 关于newtype模式、类型别名、"never"类型和动态sized类型的高级话题 +* 高级函数和闭包: 函数指针和返回闭包 -It's a panoply of Rust features with something for everyone! Let's dive in! +对所有人而言, 这都是一个介绍Rust迷人特性的宝典! 让我们走进去吧! From 26cc8f13983ca57b91cc43cb9b04552903c270a5 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 28 Jun 2017 11:42:01 +0800 Subject: [PATCH 37/59] Create ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 492 +++++++++++++++++++++++++++++++++++++ 1 file changed, 492 insertions(+) create mode 100644 src/ch19-01-unsafe-rust.md diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md new file mode 100644 index 0000000..bd9b447 --- /dev/null +++ b/src/ch19-01-unsafe-rust.md @@ -0,0 +1,492 @@ +## Unsafe Rust + +In all of the previous chapters in this book, we've been discussing code +written in Rust that has memory safety guarantees enforced at compile time. +However, Rust has a second language hiding out inside of it, unsafe Rust, which +does not enforce these memory safety guarantees. Unsafe Rust works just like +regular Rust does, but it gives you extra superpowers not available in safe +Rust code. + +Unsafe Rust exists because, by nature, static analysis is conservative. When +trying to determine if code upholds some guarantees or not, it's better to +reject some programs that are valid than it is to accept some programs that are +invalid. There are some times when your code might be okay, but Rust thinks +it's not! In these cases, you can use unsafe code to tell the compiler, "trust +me, I know what I'm doing." The downside is that you're on your own; if you get +unsafe code wrong, problems due to memory unsafety like null pointer +dereferencing can occur. + +There's another reason that Rust needs to have unsafe code: the underlying +hardware of computers is inherently not safe. If Rust didn't let you do unsafe +operations, there would be some tasks that you simply could not do. But Rust +needs to be able to let you do low-level systems programming like directly +interacting with your operating system, or even writing your own operating +system! That's part of the goals of the language. We need some way to do these +kinds of things. + +### Unsafe Superpowers + +We switch into unsafe Rust by using the `unsafe` keyword and starting a new +block that holds the unsafe code. There are four actions that you can take in +unsafe Rust that you can't in safe Rust. We call these the "unsafe +superpowers." We haven't seen most of these features yet since they're only +usable with `unsafe`! + +1. Dereferencing a raw pointer +2. Calling an unsafe function or method +3. Accessing or modifying a mutable static variable +4. Implementing an unsafe trait + +It's important to understand that `unsafe` doesn't turn off the borrow checker +or disable any other of Rust's safety checks: if you use a reference in unsafe +code, it will still be checked. The only thing the `unsafe` keyword does is +give you access to these four features that aren't checked by the compiler for +memory safety. You still get some degree of safety inside of an unsafe block! +Furthermore, `unsafe` does not mean the code inside the block is dangerous or +definitely will have memory safety problems: the intent is that you as the +programmer will ensure that the code inside an `unsafe` block will have valid +memory, since you've turned off the compiler checks. + +People are fallible, however, and mistakes will happen. By requiring these four +unsafe operations to be inside blocks annotated with `unsafe`, if you make a +mistake and get an error related to memory safety, you'll know that it has to +be related to one of the places that you opted into this unsafety. That makes +the cause of memory safety bugs much easier to find, since we know Rust is +checking all of the other code for us. To get this benefit of only having a few +places to investigate memory safety bugs, it's important to contain your unsafe +code to as small of an area as possible. Any code inside of an `unsafe` block +is suspect when debugging a memory problem: keep `unsafe` blocks small and +you'll thank yourself later since you'll have less code to investigate. + +In order to isolate unsafe code as much as possible, it's a good idea to +enclose unsafe code within a safe abstraction and provide a safe API, which +we'll be discussing once we get into unsafe functions and methods. Parts of the +standard library are implemented as safe abstractions over unsafe code that has +been audited. This prevents uses of `unsafe` from leaking out into all the +places that you or your users might want to make use of the functionality +implemented with `unsafe` code, since using a safe abstraction is safe. + +Let's talk about each of the four unsafe superpowers in turn, and along the way +we'll look at some abstractions that provide a safe interface to unsafe code. + +### Dereferencing a Raw Pointer + +Way back in Chapter 4, we first learned about references. We also learned that +the compiler ensures that references are always valid. Unsafe Rust has two new +types similar to references called *raw pointers*. Just like references, we can +have an immutable raw pointer and a mutable raw pointer. In the context of raw +pointers, "immutable" means that the pointer can't be directly dereferenced and +assigned to. Listing 19-1 shows how to create raw pointers from references: + +```rust +let mut num = 5; + +let r1 = &num as *const i32; +let r2 = &mut num as *mut i32; +``` + +Listing 19-1: Creating raw pointers from references + +The `*const T` type is an immutable raw pointer, and `*mut T` is a mutable raw +pointer. We've created raw pointers by using `as` to cast an immutable and a +mutable reference into their corresponding raw pointer types. Unlike +references, these pointers may or may not be valid. + +Listing 19-2 shows how to create a raw pointer to an arbitrary location in +memory. Trying to use arbitrary memory is undefined: there may be data at that +address, there may not be any data at that address, the compiler might optimize +the code so that there is no memory access, or your program might segfault. +There's not usually a good reason to be writing code like this, but it is +possible: + +```rust +let address = 0x012345; +let r = address as *const i32; +``` + +Listing 19-2: Creating a raw pointer to an arbitrary +memory address + +Note there's no `unsafe` block in either Listing 19-1 or 19-2. You can *create* +raw pointers in safe code, but you can't *dereference* raw pointers and read +the data being pointed to. Using the dereference operator, `*`, on a raw +pointer requires an `unsafe` block, as shown in Listing 19-3: + +```rust +let mut num = 5; + +let r1 = &num as *const i32; +let r2 = &mut num as *mut i32; + +unsafe { + println!("r1 is: {}", *r1); + println!("r2 is: {}", *r2); +} +``` + +Listing 19-3: Dereferencing raw pointers within an +`unsafe` block + +Creating a pointer can't do any harm; it's only when accessing the value that +it points at that you might end up dealing with an invalid value. + +Note also that in Listing 19-1 and 19-3 we created a `*const i32` and a `*mut +i32` that both pointed to the same memory location, that of `num`. If we had +tried to create an immutable and a mutable reference to `num` instead of raw +pointers, this would not have compiled due to the rule that says we can't have +a mutable reference at the same time as any immutable references. With raw +pointers, we are able to create a mutable pointer and an immutable pointer to +the same location, and change data through the mutable pointer, potentially +creating a data race. Be careful! + +With all of these dangers, why would we ever use raw pointers? One major use +case is interfacing with C code, as we'll see in the next section on unsafe +functions. Another case is to build up safe abstractions that the borrow +checker doesn't understand. Let's introduce unsafe functions then look at an +example of a safe abstraction that uses unsafe code. + +### Calling an Unsafe Function or Method + +The second operation that requires an unsafe block is calling an unsafe +function. Unsafe functions and methods look exactly like regular functions and +methods, but they have an extra `unsafe` out front. Bodies of unsafe functions +are effectively `unsafe` blocks. Here's an unsafe function named `dangerous`: + +```rust +unsafe fn dangerous() {} + +unsafe { + dangerous(); +} +``` + +If we try to call `dangerous` without the `unsafe` block, we'll get an error: + +```text +error[E0133]: call to unsafe function requires unsafe function or block + --> :4:5 + | +4 | dangerous(); + | ^^^^^^^^^^^ call to unsafe function +``` + +By inserting the `unsafe` block around our call to `dangerous`, we're asserting +to Rust that we've read the documentation for this function, we understand how +to use it properly, and we've verified that everything is correct. + +#### Creating a Safe Abstraction Over Unsafe Code + +As an example, let's check out some functionality from the standard library, +`split_at_mut`, and explore how we might implement it ourselves. This safe +method is defined on mutable slices, and it takes one slice and makes it into +two by splitting the slice at the index given as an argument, as demonstrated +in Listing 19-4: + +```rust +let mut v = vec![1, 2, 3, 4, 5, 6]; + +let r = &mut v[..]; + +let (a, b) = r.split_at_mut(3); + +assert_eq!(a, &mut [1, 2, 3]); +assert_eq!(b, &mut [4, 5, 6]); +``` + +Listing 19-4: Using the safe `split_at_mut` +function + +This function can't be implemented using only safe Rust. An attempt might look +like Listing 19-5. For simplicity, we're implementing `split_at_mut` as a +function rather than a method, and only for slices of `i32` values rather than +for a generic type `T`: + +```rust,ignore +fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { + let len = slice.len(); + + assert!(mid <= len); + + (&mut slice[..mid], + &mut slice[mid..]) +} +``` + +Listing 19-5: An attempted implementation of +`split_at_mut` using only safe Rust + +This function first gets the total length of the slice, then asserts that the +index given as a parameter is within the slice by checking that the parameter +is less than or equal to the length. The assertion means that if we pass an +index that's greater than the length of the slice to split at, the function +will panic before it attempts to use that index. + +Then we return two mutable slices in a tuple: one from the start of the initial +slice to the `mid` index, and another from `mid` to the end of the slice. + +If we try to compile this, we'll get an error: + +```text +error[E0499]: cannot borrow `*slice` as mutable more than once at a time + --> :6:11 + | +5 | (&mut slice[..mid], + | ----- first mutable borrow occurs here +6 | &mut slice[mid..]) + | ^^^^^ second mutable borrow occurs here +7 | } + | - first borrow ends here +``` + +Rust's borrow checker can't understand that we're borrowing different parts of +the slice; it only knows that we're borrowing from the same slice twice. +Borrowing different parts of a slice is fundamentally okay; our two `&mut +[i32]`s aren't overlapping. However, Rust isn't smart enough to know this. When +we know something is okay, but Rust doesn't, it's time to reach for unsafe code. + +Listing 19-6 shows how to use an `unsafe` block, a raw pointer, and some calls +to unsafe functions to make the implementation of `split_at_mut` work: + +```rust +use std::slice; + +fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { + let len = slice.len(); + let ptr = slice.as_mut_ptr(); + + assert!(mid <= len); + + unsafe { + (slice::from_raw_parts_mut(ptr, mid), + slice::from_raw_parts_mut(ptr.offset(mid as isize), len - mid)) + } +} +``` + +Listing 19-6: Using unsafe code in the implementation of +the `split_at_mut` function + +Recall from Chapter 4 that slices are a pointer to some data and the length of +the slice. We've often used the `len` method to get the length of a slice; we +can use the `as_mut_ptr` method to get access to the raw pointer of a slice. In +this case, since we have a mutable slice to `i32` values, `as_mut_ptr` returns +a raw pointer with the type `*mut i32`, which we've stored in the variable +`ptr`. + +The assertion that the `mid` index is within the slice stays the same. Then, +the `slice::from_raw_parts_mut` function does the reverse from the `as_mut_ptr` +and `len` methods: it takes a raw pointer and a length and creates a slice. We +call `slice::from_raw_parts_mut` to create a slice that starts from `ptr` and is +`mid` items long. Then we call the `offset` method on `ptr` with `mid` as an +argument to get a raw pointer that starts at `mid`, and we create a slice using +that pointer and the remaining number of items after `mid` as the length. + +Because slices are checked, they're safe to use once we've created them. The +function `slice::from_raw_parts_mut` is an unsafe function because it takes a +raw pointer and trusts that this pointer is valid. The `offset` method on raw +pointers is also unsafe, since it trusts that the location some offset after a +raw pointer is also a valid pointer. We've put an `unsafe` block around our +calls to `slice::from_raw_parts_mut` and `offset` to be allowed to call them, +and we can tell by looking at the code and by adding the assertion that `mid` +must be less than or equal to `len` that all the raw pointers used within the +`unsafe` block will be valid pointers to data within the slice. This is an +acceptable and appropriate use of `unsafe`. + +Note that the resulting `split_at_mut` function is safe: we didn't have to add +the `unsafe` keyword in front of it, and we can call this function from safe +Rust. We've created a safe abstraction to the unsafe code by writing an +implementation of the function that uses `unsafe` code in a safe way by only +creating valid pointers from the data this function has access to. + +In contrast, the use of `slice::from_raw_parts_mut` in Listing 19-7 would +likely crash when the slice is used. This code takes an arbitrary memory +location and creates a slice ten thousand items long: + +```rust +use std::slice; + +let address = 0x012345; +let r = address as *mut i32; + +let slice = unsafe { + slice::from_raw_parts_mut(r, 10000) +}; +``` + +Listing 19-7: Creating a slice from an arbitrary memory +location + +We don't own the memory at this arbitrary location, and there's no guarantee +that the slice this code creates contains valid `i32` values. Attempting to use +`slice` as if it was a valid slice would be undefined behavior. + +#### `extern` Functions for Calling External Code are Unsafe + +Sometimes, your Rust code may need to interact with code written in another +language. To do this, Rust has a keyword, `extern`, that facilitates creating +and using a *Foreign Function Interface* (FFI). Listing 19-8 demonstrates how +to set up an integration with a function named `some_function` defined in an +external library written in a language other than Rust. Functions declared +within `extern` blocks are always unsafe to call from Rust code: + +Filename: src/main.rs + +```rust,ignore +extern "C" { + fn some_function(); +} + +fn main() { + unsafe { some_function() }; +} +``` + +Listing 19-8: Declaring and calling an `extern` function +defined in another language + +Within the `extern "C"` block, we list the names and signatures of functions +defined in a library written in another language that we want to be able to +call.`"C"` defines which *application binary interface* (ABI) the external +function uses. The ABI defines how to call the function at the assembly level. +The `"C"` ABI is the most common, and follows the C programming language's ABI. + +Calling an external function is always unsafe. If we're calling into some other +language, that language does not enforce Rust's safety guarantees. Since Rust +can't check that the external code is safe, we are responsible for checking the +safety of the external code and indicating we have done so by using an `unsafe` +block to call external functions. + + + +##### Calling Rust Functions from Other Languages + +The `extern` keyword is also used for creating an interface that allows other +languages to call Rust functions. Instead of an `extern` block, we can add the +`extern` keyword and specifying the ABI to use just before the `fn` keyword. We +also add the `#[no_mangle]` annotation to tell the Rust compiler not to mangle +the name of this function. The `call_from_c` function in this example would be +accessible from C code, once we've compiled to a shared library and linked from +C: + +```rust +#[no_mangle] +pub extern "C" fn call_from_c() { + println!("Just called a Rust function from C!"); +} +``` + +This usage of `extern` does not require `unsafe` + + + +### Accessing or Modifying a Mutable Static Variable + +We've gone this entire book without talking about *global variables*. Many +programming languages support them, and so does Rust. However, global variables +can be problematic: for example, if you have two threads accessing the same +mutable global variable, a data race can happen. + +Global variables are called *static* in Rust. Listing 19-9 shows an example +declaration and use of a static variable with a string slice as a value: + +Filename: src/main.rs + +```rust +static HELLO_WORLD: &str = "Hello, world!"; + +fn main() { + println!("name is: {}", HELLO_WORLD); +} +``` + +Listing 19-9: Defining and using an immutable static +variable + +`static` variables are similar to constants: their names are also in +`SCREAMING_SNAKE_CASE` by convention, and we *must* annotate the variable's +type, which is `&'static str` in this case. Only references with the `'static` +lifetime may be stored in a static variable. Because of this, the Rust compiler +can figure out the lifetime by itself and we don't need to annotate it explicitly. +Accessing immutable static variables is safe. Values in a static variable have a +fixed address in memory, and using the value will always access the same data. +Constants, on the other hand, are allowed to duplicate their data whenever they +are used. + +Another way in which static variables are different from constants is that +static variables can be mutable. Both accessing and modifying mutable static +variables is unsafe. Listing 19-10 shows how to declare, access, and modify a +mutable static variable named `COUNTER`: + +Filename: src/main.rs + +```rust +static mut COUNTER: u32 = 0; + +fn add_to_count(inc: u32) { + unsafe { + COUNTER += inc; + } +} + +fn main() { + add_to_count(3); + + unsafe { + println!("COUNTER: {}", COUNTER); + } +} +``` + +Listing 19-10: Reading from or writing to a mutable +static variable is unsafe + +Just like with regular variables, we specify that a static variable should be +mutable using the `mut` keyword. Any time that we read or write from `COUNTER` +has to be within an `unsafe` block. This code compiles and prints `COUNTER: 3` +as we would expect since it's single threaded, but having multiple threads +accessing `COUNTER` would likely result in data races. + +Mutable data that is globally accessible is difficult to manage and ensure that +there are no data races, which is why Rust considers mutable static variables +to be unsafe. If possible, prefer using the concurrency techniques and +threadsafe smart pointers we discussed in Chapter 16 to have the compiler check +that data accessed from different threads is done safely. + +### Implementing an Unsafe Trait + +Finally, the last action we're only allowed to take when we use the `unsafe` +keyword is implementing an unsafe trait. We can declare that a trait is +`unsafe` by adding the `unsafe` keyword before `trait`, and then implementing +the trait must be marked as `unsafe` too, as shown in Listing 19-11: + +```rust +unsafe trait Foo { + // methods go here +} + +unsafe impl Foo for i32 { + // method implementations go here +} +``` + +Listing 19-11: Defining and implementing an unsafe +trait + +Like unsafe functions, methods in an unsafe trait have some invariant that the +compiler cannot verify. By using `unsafe impl`, we're promising that we'll +uphold these invariants. + +As an example, recall the `Sync` and `Send` marker traits from Chapter 16, and +that the compiler implements these automatically if our types are composed +entirely of `Send` and `Sync` types. If we implement a type that contains +something that's not `Send` or `Sync` such as raw pointers, and we want to mark +our type as `Send` or `Sync`, that requires using `unsafe`. Rust can't verify +that our type upholds the guarantees that a type can be safely sent across +threads or accessed from multiple threads, so we need to do those checks +ourselves and indicate as such with `unsafe`. + +Using `unsafe` to take one of these four actions isn't wrong or frowned upon, +but it is trickier to get `unsafe` code correct since the compiler isn't able +to help uphold memory safety. When you have a reason to use `unsafe` code, +however, it's possible to do so, and having the explicit `unsafe` annotation +makes it easier to track down the source of problems if they occur. From a025a50a099b0377a7c089ec2fd5cc4b0732949a Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 28 Jun 2017 11:44:34 +0800 Subject: [PATCH 38/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index bd9b447..eaf700c 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -1,4 +1,4 @@ -## Unsafe Rust +## 不安全的Rust In all of the previous chapters in this book, we've been discussing code written in Rust that has memory safety guarantees enforced at compile time. From 123b010f6e7bdab784641e40b2f6a6310d53689d Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 28 Jun 2017 11:45:48 +0800 Subject: [PATCH 39/59] Update ch19-00-advanced-features.md --- src/ch19-00-advanced-features.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ch19-00-advanced-features.md b/src/ch19-00-advanced-features.md index daf2787..9c637e7 100644 --- a/src/ch19-00-advanced-features.md +++ b/src/ch19-00-advanced-features.md @@ -4,10 +4,10 @@ 本章将覆盖如下内容: -* Unsafe Rust: 用于当你需要弃用Rust的某些保证时你告诉编译器你将负责维护某些保证 +* 不安全的Rust: 用于当你需要弃用Rust的某些保证时你告诉编译器你将负责维护某些保证 * 高级生命周期: 应对复杂情况的额外的生命周期语法 * 高级Traits: 关联类型, 默认类型参数, 完全合格的句法(fully qualified - syntax), 超级trait(supertraits), 和与trait关联的新类型模式 + syntax), 超级trait(supertraits), 和与trait关联的newtype模式 * 高级类型: 关于newtype模式、类型别名、"never"类型和动态sized类型的高级话题 * 高级函数和闭包: 函数指针和返回闭包 From ec0e83f61bef200fd33ed073b9a15d7acc256920 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 28 Jun 2017 11:47:50 +0800 Subject: [PATCH 40/59] Update ch19-00-advanced-features.md --- src/ch19-00-advanced-features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch19-00-advanced-features.md b/src/ch19-00-advanced-features.md index 9c637e7..6d36930 100644 --- a/src/ch19-00-advanced-features.md +++ b/src/ch19-00-advanced-features.md @@ -11,4 +11,4 @@ * 高级类型: 关于newtype模式、类型别名、"never"类型和动态sized类型的高级话题 * 高级函数和闭包: 函数指针和返回闭包 -对所有人而言, 这都是一个介绍Rust迷人特性的宝典! 让我们走进去吧! +对所有人而言, 这都是一个介绍Rust迷人特性的宝典! 让我们翻开它吧! From 2ac5034bc2339bb0d28316ed00179afb38c06b29 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 28 Jun 2017 23:56:21 +0800 Subject: [PATCH 41/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index eaf700c..0735667 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -1,24 +1,10 @@ ## 不安全的Rust -In all of the previous chapters in this book, we've been discussing code -written in Rust that has memory safety guarantees enforced at compile time. -However, Rust has a second language hiding out inside of it, unsafe Rust, which -does not enforce these memory safety guarantees. Unsafe Rust works just like -regular Rust does, but it gives you extra superpowers not available in safe -Rust code. +在本书之前的章节, 我们讨论了Rust代码在编译时会强制保证内存安全. 然而, Rust还有另一个隐藏的语言特性, 这就是不安全的Rust, 它不会担保内存安全. 不安全的Rust和常规Rust代码无异, 但是它会给你安全的Rust代码不具备的超能力. -Unsafe Rust exists because, by nature, static analysis is conservative. When -trying to determine if code upholds some guarantees or not, it's better to -reject some programs that are valid than it is to accept some programs that are -invalid. There are some times when your code might be okay, but Rust thinks -it's not! In these cases, you can use unsafe code to tell the compiler, "trust -me, I know what I'm doing." The downside is that you're on your own; if you get -unsafe code wrong, problems due to memory unsafety like null pointer -dereferencing can occur. +不安全的Rust之所以存在, 本质上是因为编译器对代码的静态分析趋于保守. 代码何时保证内存安全, 何时放权这种担保呢? 把合法的代码拒绝掉通常比接纳非法的代码要好一点. 有些时候你的代码的确没问题, 但是Rust却不这样认为! 这时你可以用不安全的代码告诉编译器, "相信我吧, 我知道我在做什么." 这样缺陷可能就在于你自己了; 如果你的不安全代码发生了错误, 比如对null指针解引用就可能会引发内存不安全的大问题. -There's another reason that Rust needs to have unsafe code: the underlying -hardware of computers is inherently not safe. If Rust didn't let you do unsafe -operations, there would be some tasks that you simply could not do. But Rust +还有另一个Rust需要不安全代码的原因: 底层电脑硬件固有的不安全性. 如果Rust不让你执行不安全的操作, 那么有些任务你就完成不了. But Rust needs to be able to let you do low-level systems programming like directly interacting with your operating system, or even writing your own operating system! That's part of the goals of the language. We need some way to do these From be30e242e747e855c02f77c0335e450f631379b9 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Thu, 29 Jun 2017 11:43:22 +0800 Subject: [PATCH 42/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 56 ++++++++------------------------------ 1 file changed, 12 insertions(+), 44 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 0735667..3d989b0 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -4,58 +4,26 @@ 不安全的Rust之所以存在, 本质上是因为编译器对代码的静态分析趋于保守. 代码何时保证内存安全, 何时放权这种担保呢? 把合法的代码拒绝掉通常比接纳非法的代码要好一点. 有些时候你的代码的确没问题, 但是Rust却不这样认为! 这时你可以用不安全的代码告诉编译器, "相信我吧, 我知道我在做什么." 这样缺陷可能就在于你自己了; 如果你的不安全代码发生了错误, 比如对null指针解引用就可能会引发内存不安全的大问题. -还有另一个Rust需要不安全代码的原因: 底层电脑硬件固有的不安全性. 如果Rust不让你执行不安全的操作, 那么有些任务你就完成不了. But Rust -needs to be able to let you do low-level systems programming like directly -interacting with your operating system, or even writing your own operating -system! That's part of the goals of the language. We need some way to do these -kinds of things. +还有另一个Rust需要不安全代码的原因: 底层电脑硬件固有的不安全性. 如果Rust不让你执行不安全的操作, 那么有些任务你就完成不了. 但是Rust需要你能够做像直接与操作系统交互甚至是写你自己的操作系统这样的底层操作! 这也是Rust语言的一部分目标, 所以我们需要一些来做这些事情的方法. -### Unsafe Superpowers +### 不安全的神力 -We switch into unsafe Rust by using the `unsafe` keyword and starting a new -block that holds the unsafe code. There are four actions that you can take in -unsafe Rust that you can't in safe Rust. We call these the "unsafe -superpowers." We haven't seen most of these features yet since they're only -usable with `unsafe`! +我们通过使用`unsafe`关键字开启一个持有不安全代码的代码块来切换到不安全的Rust. 你可以在不安全的Rust中进行四个安全的Rust做不到的操作. 我们把它们称作"不安全的神力". 之前我们没见过这几个特性是因为它们只用在`unsafe`代码块中! 它们是: -1. Dereferencing a raw pointer -2. Calling an unsafe function or method -3. Accessing or modifying a mutable static variable -4. Implementing an unsafe trait +1. 解引用原生指针 +2. 调用一个不安全的函数或方法 +3. 访问或修改一个不可变的静态变量 +4. 实现一个不安全的trait -It's important to understand that `unsafe` doesn't turn off the borrow checker -or disable any other of Rust's safety checks: if you use a reference in unsafe -code, it will still be checked. The only thing the `unsafe` keyword does is -give you access to these four features that aren't checked by the compiler for -memory safety. You still get some degree of safety inside of an unsafe block! -Furthermore, `unsafe` does not mean the code inside the block is dangerous or -definitely will have memory safety problems: the intent is that you as the -programmer will ensure that the code inside an `unsafe` block will have valid -memory, since you've turned off the compiler checks. +记住这一点很重要, `unsafe`不会关掉借用检查器也不会禁用其它的Rust安全性检查: 如果你在不安全的代码中用了引用, 它仍将会被检查. `unsafe`关键字做的唯一的一件事是让你存取编译器因内存安全性而没去检查的上述四个特性.在一个unsafe代码块中你仍然会获得某种程度的安全性! 此外, `unsafe`并不是说代码块中的代码是危险的或者有内存安全性问题: 它只是表明作为程序员的关掉了编译器检查, 你将确保`unsafe`代码块会拥有合理的内存. -People are fallible, however, and mistakes will happen. By requiring these four -unsafe operations to be inside blocks annotated with `unsafe`, if you make a -mistake and get an error related to memory safety, you'll know that it has to -be related to one of the places that you opted into this unsafety. That makes -the cause of memory safety bugs much easier to find, since we know Rust is -checking all of the other code for us. To get this benefit of only having a few -places to investigate memory safety bugs, it's important to contain your unsafe -code to as small of an area as possible. Any code inside of an `unsafe` block -is suspect when debugging a memory problem: keep `unsafe` blocks small and -you'll thank yourself later since you'll have less code to investigate. +人是会犯错误的, 错误总会发生. 在`unsafe`代码块中执行上述四个不安全的操作时, 如果你犯了错误并得到一个内存安全性的错误, 你必定会知道它与你使用不安全的代码有关. 这样就更容易处理内存安全性的bug, 因为Rust已经帮我们把其它的代码做了检查. 能缩小排查内存安全性bug的出现区域当然好, 所以尽量缩小你的不安全代码的数量吧. 当修正内存安全问题时, `unsafe`代码块中的任意代码都可能出错: 所以让`unsafe`代码块尽可能的小吧, 以后你需要排查的代码也会少一些. -In order to isolate unsafe code as much as possible, it's a good idea to -enclose unsafe code within a safe abstraction and provide a safe API, which -we'll be discussing once we get into unsafe functions and methods. Parts of the -standard library are implemented as safe abstractions over unsafe code that has -been audited. This prevents uses of `unsafe` from leaking out into all the -places that you or your users might want to make use of the functionality -implemented with `unsafe` code, since using a safe abstraction is safe. +为了尽可能隔离不安全的代码, 在安全的抽象中包含不安全的代码并提供一个安全的API是一个好主意, 当我们进入不安全的函数和方法的学习时我们会讨论它. 标准库中有些不安全代码被实现为安全的抽象, 它们中的部分已被审核过了. 当你或者你的用户使用通过`unsafe`代码实现的功能时, 因为使用一个安全的抽象时安全的, 这样就可以避免到处都是`unsafe`字样. -Let's talk about each of the four unsafe superpowers in turn, and along the way -we'll look at some abstractions that provide a safe interface to unsafe code. +让我们按顺序讲介绍上述四个不安全的神力, 同时我们会见到一些抽象, 它们为不安全代码的代码提供了安全的接口. -### Dereferencing a Raw Pointer +### 解引用原生指针 Way back in Chapter 4, we first learned about references. We also learned that the compiler ensures that references are always valid. Unsafe Rust has two new From d61ad50619832ccd2fbe1b1fdccdc1c12c4c2253 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Thu, 29 Jun 2017 11:44:52 +0800 Subject: [PATCH 43/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 3d989b0..caf94f7 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -21,7 +21,7 @@ 为了尽可能隔离不安全的代码, 在安全的抽象中包含不安全的代码并提供一个安全的API是一个好主意, 当我们进入不安全的函数和方法的学习时我们会讨论它. 标准库中有些不安全代码被实现为安全的抽象, 它们中的部分已被审核过了. 当你或者你的用户使用通过`unsafe`代码实现的功能时, 因为使用一个安全的抽象时安全的, 这样就可以避免到处都是`unsafe`字样. -让我们按顺序讲介绍上述四个不安全的神力, 同时我们会见到一些抽象, 它们为不安全代码的代码提供了安全的接口. +让我们按顺序依次介绍上述四个不安全的神力, 同时我们会见到一些抽象, 它们为不安全代码的代码提供了安全的接口. ### 解引用原生指针 From 6b050e7684b63085d0bb329ececffd832022e8a5 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Thu, 29 Jun 2017 11:45:56 +0800 Subject: [PATCH 44/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index caf94f7..1027f2b 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -21,7 +21,7 @@ 为了尽可能隔离不安全的代码, 在安全的抽象中包含不安全的代码并提供一个安全的API是一个好主意, 当我们进入不安全的函数和方法的学习时我们会讨论它. 标准库中有些不安全代码被实现为安全的抽象, 它们中的部分已被审核过了. 当你或者你的用户使用通过`unsafe`代码实现的功能时, 因为使用一个安全的抽象时安全的, 这样就可以避免到处都是`unsafe`字样. -让我们按顺序依次介绍上述四个不安全的神力, 同时我们会见到一些抽象, 它们为不安全代码的代码提供了安全的接口. +让我们按顺序依次介绍上述四个不安全的神力, 同时我们会见到一些抽象, 它们为不安全的代码提供了安全的接口. ### 解引用原生指针 From 67728a2a211a33fe3914d284b147d9b303c2111d Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Thu, 29 Jun 2017 11:48:28 +0800 Subject: [PATCH 45/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 1027f2b..112c976 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -19,7 +19,7 @@ 人是会犯错误的, 错误总会发生. 在`unsafe`代码块中执行上述四个不安全的操作时, 如果你犯了错误并得到一个内存安全性的错误, 你必定会知道它与你使用不安全的代码有关. 这样就更容易处理内存安全性的bug, 因为Rust已经帮我们把其它的代码做了检查. 能缩小排查内存安全性bug的出现区域当然好, 所以尽量缩小你的不安全代码的数量吧. 当修正内存安全问题时, `unsafe`代码块中的任意代码都可能出错: 所以让`unsafe`代码块尽可能的小吧, 以后你需要排查的代码也会少一些. -为了尽可能隔离不安全的代码, 在安全的抽象中包含不安全的代码并提供一个安全的API是一个好主意, 当我们进入不安全的函数和方法的学习时我们会讨论它. 标准库中有些不安全代码被实现为安全的抽象, 它们中的部分已被审核过了. 当你或者你的用户使用通过`unsafe`代码实现的功能时, 因为使用一个安全的抽象时安全的, 这样就可以避免到处都是`unsafe`字样. +为了尽可能隔离不安全的代码, 在安全的抽象中包含不安全的代码并提供一个安全的API是一个好主意, 当我们学习不安全的函数和方法时我们会讨论它. 标准库中有些不安全的代码被实现为安全的抽象, 它们中的部分已被审核过了. 当你或者你的用户使用通过`unsafe`代码实现的功能时, 因为使用一个安全的抽象是安全的, 这样就可以避免到处都是`unsafe`字样. 让我们按顺序依次介绍上述四个不安全的神力, 同时我们会见到一些抽象, 它们为不安全的代码提供了安全的接口. From 6b6320961369097290c0f6eeffacbf01b1528117 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Thu, 29 Jun 2017 11:51:53 +0800 Subject: [PATCH 46/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 112c976..0196a3c 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -15,9 +15,9 @@ 3. 访问或修改一个不可变的静态变量 4. 实现一个不安全的trait -记住这一点很重要, `unsafe`不会关掉借用检查器也不会禁用其它的Rust安全性检查: 如果你在不安全的代码中用了引用, 它仍将会被检查. `unsafe`关键字做的唯一的一件事是让你存取编译器因内存安全性而没去检查的上述四个特性.在一个unsafe代码块中你仍然会获得某种程度的安全性! 此外, `unsafe`并不是说代码块中的代码是危险的或者有内存安全性问题: 它只是表明作为程序员的关掉了编译器检查, 你将确保`unsafe`代码块会拥有合理的内存. +记住这一点很重要, `unsafe`不会关掉借用检查器也不会禁用其它的Rust安全性检查: 如果你在不安全的代码中用了引用, 它仍将会被检查. `unsafe`关键字做的唯一的一件事是让你存取编译器因内存安全性而没去检查的上述四个特性.在一个unsafe代码块中你仍然会获得某种程度的安全性! 此外, `unsafe`并不是说代码块中的代码是危险的或者有内存安全性问题: 它只是表明作为程序员的你关掉了编译器检查, 你将确保`unsafe`代码块会拥有合理的内存. -人是会犯错误的, 错误总会发生. 在`unsafe`代码块中执行上述四个不安全的操作时, 如果你犯了错误并得到一个内存安全性的错误, 你必定会知道它与你使用不安全的代码有关. 这样就更容易处理内存安全性的bug, 因为Rust已经帮我们把其它的代码做了检查. 能缩小排查内存安全性bug的出现区域当然好, 所以尽量缩小你的不安全代码的数量吧. 当修正内存安全问题时, `unsafe`代码块中的任意代码都可能出错: 所以让`unsafe`代码块尽可能的小吧, 以后你需要排查的代码也会少一些. +人是会犯错误的, 错误总会发生. 在`unsafe`代码块中执行上述四个不安全的操作时, 如果你犯了错误并得到一个内存安全性的错误, 你必定会知道它与你使用不安全的代码有关. 这样就更容易处理内存安全性的bug, 因为Rust已经帮我们把其它的代码做了检查. 能缩小排查内存安全性bug的出现区域当然好, 所以尽量缩小你的不安全代码的数量吧. 当修正内存安全问题时, `unsafe`代码块中的任何代码都可能出错: 所以让`unsafe`代码块尽可能的小吧, 以后你需要排查的代码也会少一些. 为了尽可能隔离不安全的代码, 在安全的抽象中包含不安全的代码并提供一个安全的API是一个好主意, 当我们学习不安全的函数和方法时我们会讨论它. 标准库中有些不安全的代码被实现为安全的抽象, 它们中的部分已被审核过了. 当你或者你的用户使用通过`unsafe`代码实现的功能时, 因为使用一个安全的抽象是安全的, 这样就可以避免到处都是`unsafe`字样. From 81f0751fc6973b7ed688ca88805cddfb5edd03e0 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Thu, 29 Jun 2017 11:54:20 +0800 Subject: [PATCH 47/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 0196a3c..eeabc83 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -2,7 +2,7 @@ 在本书之前的章节, 我们讨论了Rust代码在编译时会强制保证内存安全. 然而, Rust还有另一个隐藏的语言特性, 这就是不安全的Rust, 它不会担保内存安全. 不安全的Rust和常规Rust代码无异, 但是它会给你安全的Rust代码不具备的超能力. -不安全的Rust之所以存在, 本质上是因为编译器对代码的静态分析趋于保守. 代码何时保证内存安全, 何时放权这种担保呢? 把合法的代码拒绝掉通常比接纳非法的代码要好一点. 有些时候你的代码的确没问题, 但是Rust却不这样认为! 这时你可以用不安全的代码告诉编译器, "相信我吧, 我知道我在做什么." 这样缺陷可能就在于你自己了; 如果你的不安全代码发生了错误, 比如对null指针解引用就可能会引发内存不安全的大问题. +不安全的Rust之所以存在, 本质上是因为编译器对代码的静态分析趋于保守. 代码何时保证内存安全, 何时放权这种担保呢? 把合法的代码拒绝掉通常比接纳非法的代码要好一点. 有些时候你的代码的确没问题, 但是Rust却不这样认为! 这时你可以用不安全的代码告诉编译器, "相信我吧, 我知道我在做什么." 这样缺陷可能就在于你自己了; 如果你的不安全代码发生了错误, 比如对null指针解引用就可能会引发内存出错的大问题. 还有另一个Rust需要不安全代码的原因: 底层电脑硬件固有的不安全性. 如果Rust不让你执行不安全的操作, 那么有些任务你就完成不了. 但是Rust需要你能够做像直接与操作系统交互甚至是写你自己的操作系统这样的底层操作! 这也是Rust语言的一部分目标, 所以我们需要一些来做这些事情的方法. From 16cf801ca8cda9422fcca37e61832142da562cba Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Thu, 29 Jun 2017 18:24:17 +0800 Subject: [PATCH 48/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index eeabc83..ae862e0 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -25,12 +25,7 @@ ### 解引用原生指针 -Way back in Chapter 4, we first learned about references. We also learned that -the compiler ensures that references are always valid. Unsafe Rust has two new -types similar to references called *raw pointers*. Just like references, we can -have an immutable raw pointer and a mutable raw pointer. In the context of raw -pointers, "immutable" means that the pointer can't be directly dereferenced and -assigned to. Listing 19-1 shows how to create raw pointers from references: +回到第4章, 我们在哪里学习了引用. 我们知道编译器会确保引用永远合法. 不安全的Rust有两个类似于引用的被称为*原生指针*(*raw pointers*)的新类型. 和引用一样, 我们可以有一个不可变的原生指针和一个可变的原生指针. 在原生指针的上下文中, "不可变"意味着指针不能直接被解引用和被赋值. 例19-1演示了如何通过引用来创建一个原生指针: ```rust let mut num = 5; @@ -39,32 +34,21 @@ let r1 = &num as *const i32; let r2 = &mut num as *mut i32; ``` -Listing 19-1: Creating raw pointers from references +例19-1: 通过引用创建原生指针 -The `*const T` type is an immutable raw pointer, and `*mut T` is a mutable raw -pointer. We've created raw pointers by using `as` to cast an immutable and a -mutable reference into their corresponding raw pointer types. Unlike -references, these pointers may or may not be valid. +上例中`*const T`类型是一个不可变的原生指针, `*mut T`是一个可变的原生指针. 我们通过使用`as`把一个可变的和一个不可变的引用转换成它们对应的原生指针类型来创建原生指针. 与引用不同, 这些指针的合法性不能得到保证. -Listing 19-2 shows how to create a raw pointer to an arbitrary location in -memory. Trying to use arbitrary memory is undefined: there may be data at that -address, there may not be any data at that address, the compiler might optimize -the code so that there is no memory access, or your program might segfault. -There's not usually a good reason to be writing code like this, but it is -possible: +例19-2演示了如何创建一个指向内存中任意地址的原生指针. 试图随便访问内存地址所带来的结果是难以预测的: 也许在那个地址处有数据, 也许在那个地址处没有任何数据, 编译器也可能会优化代码导致那块内存不能访问, 亦或你的程序可能会发生段错误. 虽然可以写出下面的代码, 但是通常找不到好的理由来这样做: ```rust let address = 0x012345; let r = address as *const i32; ``` -Listing 19-2: Creating a raw pointer to an arbitrary -memory address +例子19-2: 创建一个指向任意内存地址的原生指针 -Note there's no `unsafe` block in either Listing 19-1 or 19-2. You can *create* -raw pointers in safe code, but you can't *dereference* raw pointers and read -the data being pointed to. Using the dereference operator, `*`, on a raw -pointer requires an `unsafe` block, as shown in Listing 19-3: +注意在例19-1和19-2中没有`unsafe`代码块. 你可以在安全代码中*创建*原生指针 +raw pointers in safe code, 但是你不能在安全代码中*解引用*(*dereference*)原生指针来读取被指针指向的数据. 如例19-3所示, 对原生指针解引用需要在`unsafe`代码块中使用解引用操作符`*`: ```rust let mut num = 5; @@ -78,8 +62,7 @@ unsafe { } ``` -Listing 19-3: Dereferencing raw pointers within an -`unsafe` block +例19-3: 在`unsafe`代码块中解引用原生指针 Creating a pointer can't do any harm; it's only when accessing the value that it points at that you might end up dealing with an invalid value. From c6e755a99008adac6e6d07cfeb7858f1ed2be70d Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Thu, 29 Jun 2017 23:34:39 +0800 Subject: [PATCH 49/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index ae862e0..4f6ad2d 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -64,30 +64,15 @@ unsafe { 例19-3: 在`unsafe`代码块中解引用原生指针 -Creating a pointer can't do any harm; it's only when accessing the value that -it points at that you might end up dealing with an invalid value. +创建一个指针不会造成任何危险; 只有在你访问指针指向的值时可能出问题, 因为你可能会用它处理无效的值. -Note also that in Listing 19-1 and 19-3 we created a `*const i32` and a `*mut -i32` that both pointed to the same memory location, that of `num`. If we had -tried to create an immutable and a mutable reference to `num` instead of raw -pointers, this would not have compiled due to the rule that says we can't have -a mutable reference at the same time as any immutable references. With raw -pointers, we are able to create a mutable pointer and an immutable pointer to -the same location, and change data through the mutable pointer, potentially -creating a data race. Be careful! +注意在19-1和19-3中我们创建的一个`*const i32`和一个`*mut i32`都指向同一个内存位置, 也就是`num`. 如果我们尝试创建一个不可变的和可变的`num`的引用而不是原生指针, 这就不能被编译, 因为我们不能在使用了不可变引用的同时再对同一个值进行可变引用. 通过原生指针, 我们能创建指向同一个内存位置的可变指针和不可变指针, 我们可以通过可变指针来改变数据, 但是要小心, 因为这可能会产生数据竞争! -With all of these dangers, why would we ever use raw pointers? One major use -case is interfacing with C code, as we'll see in the next section on unsafe -functions. Another case is to build up safe abstractions that the borrow -checker doesn't understand. Let's introduce unsafe functions then look at an -example of a safe abstraction that uses unsafe code. +既然存在这么多的危险, 为什么我们还要使用原生指针呢? 一个主要的原因是为了与C代码交互, 在下一节的不安全函数里我们将会看到. 另一个原因是创建一个借用检查器理解不了的安全的抽象. 下面让我们介绍不安全的函数, 然后再看一个使用了不安全代码的安全的抽象的例子. -### Calling an Unsafe Function or Method +### 调用一个不安全的函数或方法 -The second operation that requires an unsafe block is calling an unsafe -function. Unsafe functions and methods look exactly like regular functions and -methods, but they have an extra `unsafe` out front. Bodies of unsafe functions -are effectively `unsafe` blocks. Here's an unsafe function named `dangerous`: +需要一个不安全的代码块的才能执行的第二个操作是调用不安全的函数. 不安全的函数和方法与常规的函数和方法看上去没有什么异样, 只是他们前面有一个额外的`unsafe`关键字. 不安全的函数的函数体自然是`unsafe`的代码块. 下例是一个名叫`dangerous`的不安全的函数: ```rust unsafe fn dangerous() {} @@ -97,7 +82,7 @@ unsafe { } ``` -If we try to call `dangerous` without the `unsafe` block, we'll get an error: +如果不用`unsafe`代码块来调用`dangerous`, 我们将会得到下面的错误: ```text error[E0133]: call to unsafe function requires unsafe function or block @@ -107,11 +92,9 @@ error[E0133]: call to unsafe function requires unsafe function or block | ^^^^^^^^^^^ call to unsafe function ``` -By inserting the `unsafe` block around our call to `dangerous`, we're asserting -to Rust that we've read the documentation for this function, we understand how -to use it properly, and we've verified that everything is correct. +通过把对`dangerous`的调用放到`unsafe`代码块中, 我们表明我们已经阅读了该函数的文档, 我们明白如何正确的使用它, 并且我们已经验证了调用的正确性. -#### Creating a Safe Abstraction Over Unsafe Code +#### 创建一个不安全的代码上的安全的抽象 As an example, let's check out some functionality from the standard library, `split_at_mut`, and explore how we might implement it ourselves. This safe From 32d6cff3a81dd49b978364948330bc702dfbb05a Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Fri, 30 Jun 2017 12:02:36 +0800 Subject: [PATCH 50/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 44 ++++++++++---------------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 4f6ad2d..87e900f 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -96,11 +96,7 @@ error[E0133]: call to unsafe function requires unsafe function or block #### 创建一个不安全的代码上的安全的抽象 -As an example, let's check out some functionality from the standard library, -`split_at_mut`, and explore how we might implement it ourselves. This safe -method is defined on mutable slices, and it takes one slice and makes it into -two by splitting the slice at the index given as an argument, as demonstrated -in Listing 19-4: +让我们用标准库中的某个函数比如`split_at_mut`来举个例子, 然后来探讨我们如何自己来实现它. 这个方法被定义在一个可变的切片(slice)上, 它通过参数指定的索引把一个切片分割成两个, 如例19-4所示: ```rust let mut v = vec![1, 2, 3, 4, 5, 6]; @@ -113,13 +109,9 @@ assert_eq!(a, &mut [1, 2, 3]); assert_eq!(b, &mut [4, 5, 6]); ``` -Listing 19-4: Using the safe `split_at_mut` -function +例19-4: 使用安全的`split_at_mut`函数 -This function can't be implemented using only safe Rust. An attempt might look -like Listing 19-5. For simplicity, we're implementing `split_at_mut` as a -function rather than a method, and only for slices of `i32` values rather than -for a generic type `T`: +用安全的Rust代码是不能实现这个函数的. 如果要试一下用安全的Rust来实现它可以参考例19-5. 简单起见, 我们把`split_at_mut`实现成一个函数而不是一个方法, 这个函数只处理`i32`类型的切片而不是泛型类型`T`的切片: ```rust,ignore fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { @@ -132,19 +124,13 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { } ``` -Listing 19-5: An attempted implementation of -`split_at_mut` using only safe Rust +例19-5: 尝试用安全的Rust来实现`split_at_mut` -This function first gets the total length of the slice, then asserts that the -index given as a parameter is within the slice by checking that the parameter -is less than or equal to the length. The assertion means that if we pass an -index that's greater than the length of the slice to split at, the function -will panic before it attempts to use that index. +该函数先取得切片(slice)的长度, 然后通过检查参数是否小于或等于这个长度来断言参数给定的索引位于切片(slice)当中. 这个断言意外着如果我们传入的索引比要分割的切片(slice)的长度大, 这个函数就会在使用这个索引前中断(panic). -Then we return two mutable slices in a tuple: one from the start of the initial -slice to the `mid` index, and another from `mid` to the end of the slice. +接着我们在一个元组中返回两个可变的切片(slice): 一个从被分割的切片的头部开始直到`mid`索引的前一个元素中止, 另一个从被分割的切片的`mid`索引开始直到被分割的切片的末尾结束. -If we try to compile this, we'll get an error: +如果我们编译上面的代码, 我们将得到一个错误: ```text error[E0499]: cannot borrow `*slice` as mutable more than once at a time @@ -158,14 +144,9 @@ error[E0499]: cannot borrow `*slice` as mutable more than once at a time | - first borrow ends here ``` -Rust's borrow checker can't understand that we're borrowing different parts of -the slice; it only knows that we're borrowing from the same slice twice. -Borrowing different parts of a slice is fundamentally okay; our two `&mut -[i32]`s aren't overlapping. However, Rust isn't smart enough to know this. When -we know something is okay, but Rust doesn't, it's time to reach for unsafe code. +Rust的借用检查器不能理解为什么我们要借用这个切片(slice)的不同部分; 它只知道我们对同一个切片借用了两次. 借用一个切片(slice)的不同部分在功能上是没问题的; 而且我们的两个`&mut [i32]`也没有重叠. 但是Rust并没有聪明到能明白这一点. 当我们知道有些东西是可以的但是Rust却不知道的时候就是时候使用不安全的代码了. -Listing 19-6 shows how to use an `unsafe` block, a raw pointer, and some calls -to unsafe functions to make the implementation of `split_at_mut` work: +例子19-6演示了如何用一个`unsafe`代码块、 一个原生指针和一个不安全的函数调用来实现`split_at_mut`: ```rust use std::slice; @@ -183,12 +164,9 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { } ``` -Listing 19-6: Using unsafe code in the implementation of -the `split_at_mut` function +例19-6: 用不安全的代码来实现`split_at_mut` -Recall from Chapter 4 that slices are a pointer to some data and the length of -the slice. We've often used the `len` method to get the length of a slice; we -can use the `as_mut_ptr` method to get access to the raw pointer of a slice. In +回顾一下第4章, 切片(slice)是一个指向某个数据的指针和这个切片(slice)的长度. 我们经常用`len`方法来取得切片的长度; 也可以用`as_mut_ptr`方法来访问切片的原生指针. In this case, since we have a mutable slice to `i32` values, `as_mut_ptr` returns a raw pointer with the type `*mut i32`, which we've stored in the variable `ptr`. From 78ce55af5040a89e192cb4d564d6e6ab375ab7f1 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Fri, 30 Jun 2017 23:38:25 +0800 Subject: [PATCH 51/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 43 +++++++------------------------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 87e900f..0e64140 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -166,39 +166,15 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { 例19-6: 用不安全的代码来实现`split_at_mut` -回顾一下第4章, 切片(slice)是一个指向某个数据的指针和这个切片(slice)的长度. 我们经常用`len`方法来取得切片的长度; 也可以用`as_mut_ptr`方法来访问切片的原生指针. In -this case, since we have a mutable slice to `i32` values, `as_mut_ptr` returns -a raw pointer with the type `*mut i32`, which we've stored in the variable -`ptr`. +回顾一下第4章, 切片(slice)是一个指向某个数据的指针和这个切片(slice)的长度. 我们经常用`len`方法来取得切片的长度; 也可以用`as_mut_ptr`方法来访问切片的原生指针. 在这个例子里, 因为我们有一个可变的`i32`类型的切片, `as_mut_ptr`返回一个`*mut i32`类型的原生指针, 我们把它存放在变量`ptr`里. -The assertion that the `mid` index is within the slice stays the same. Then, -the `slice::from_raw_parts_mut` function does the reverse from the `as_mut_ptr` -and `len` methods: it takes a raw pointer and a length and creates a slice. We -call `slice::from_raw_parts_mut` to create a slice that starts from `ptr` and is -`mid` items long. Then we call the `offset` method on `ptr` with `mid` as an -argument to get a raw pointer that starts at `mid`, and we create a slice using -that pointer and the remaining number of items after `mid` as the length. +对索引`mid`合法性的断言上面已经介绍过了. 函数`slice::from_raw_parts_mut`的行为与`as_mut_ptr`和`len`方法相反: 它以一个原生指针和一个长度为参数并返回一个切片(slice). 我们调用`slice::from_raw_parts_mut`来创建一个从`ptr`开始且拥有`mid`个元素的切片. 然后我们以`mid`为参数调用`prt`上的`offset`方法来得到一个从索引`mid`开始的原生指针, 然后我们用这个原生指针和索引`mid`之后的元素个数为参数创建一个切片. -Because slices are checked, they're safe to use once we've created them. The -function `slice::from_raw_parts_mut` is an unsafe function because it takes a -raw pointer and trusts that this pointer is valid. The `offset` method on raw -pointers is also unsafe, since it trusts that the location some offset after a -raw pointer is also a valid pointer. We've put an `unsafe` block around our -calls to `slice::from_raw_parts_mut` and `offset` to be allowed to call them, -and we can tell by looking at the code and by adding the assertion that `mid` -must be less than or equal to `len` that all the raw pointers used within the -`unsafe` block will be valid pointers to data within the slice. This is an -acceptable and appropriate use of `unsafe`. +因为切片(slice)会被检查, 所以一旦我们创建了它就可以安全使用. 函数`slice::from_raw_parts_mut`是一个不安全的函数因为它有一个原生指针参数, 而且它相信这个指针是有效的. 原生指针的`offset`方法也是不安全的, 因为它相信一个原生指针的位置偏移一些后也是一个有效的指针. 为了能调用`slice::from_raw_parts_mut`和`offset`, 我们把他们的调用放到一个`unsafe`代码块中, 我们可以通过查看代码并添加`mid`不大于`len`的断言来表明`unsafe`代码块中的原生指针是指向切片中的数据的有效指针. 这是一个`unsafe`恰当用法. -Note that the resulting `split_at_mut` function is safe: we didn't have to add -the `unsafe` keyword in front of it, and we can call this function from safe -Rust. We've created a safe abstraction to the unsafe code by writing an -implementation of the function that uses `unsafe` code in a safe way by only -creating valid pointers from the data this function has access to. +注意结果`split_at_mut`函数是安全的: 我们不用在它的前面添加`unsafe`关键字, 并且我们可以从安全的Rust代码中调用它. 我们通过写一个使用了`unsafe`代码的函数来创建不安全代码的安全抽象, 上例用一种安全的方式通过函数访问的数据来创建有效的指针. -In contrast, the use of `slice::from_raw_parts_mut` in Listing 19-7 would -likely crash when the slice is used. This code takes an arbitrary memory -location and creates a slice ten thousand items long: +相反, 当使用切片(slice)时, 例19-7中`slice::from_raw_parts_mut`的用法很可能会崩溃. 下面的代码用一个随意的内存地址来创建一个有10000个元素的切片: ```rust use std::slice; @@ -211,14 +187,11 @@ let slice = unsafe { }; ``` -Listing 19-7: Creating a slice from an arbitrary memory -location +例19-7: 通过一个任意的内存位置来创建一个切片 -We don't own the memory at this arbitrary location, and there's no guarantee -that the slice this code creates contains valid `i32` values. Attempting to use -`slice` as if it was a valid slice would be undefined behavior. +我们不能拥有任意地址的内存, 也不能保证这个代码创建的切片会包含有效的`i32`类型的值. 试图使用臆测是有效切片的`slice`的行为是难以预测的. -#### `extern` Functions for Calling External Code are Unsafe +#### 调用外部代码的`extern`函数是不安全的 Sometimes, your Rust code may need to interact with code written in another language. To do this, Rust has a keyword, `extern`, that facilitates creating From 8bb5c8fa97b1e0b9adc949145b0c810b3623f135 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Sun, 2 Jul 2017 22:39:05 +0800 Subject: [PATCH 52/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 0e64140..c920f7a 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -193,8 +193,7 @@ let slice = unsafe { #### 调用外部代码的`extern`函数是不安全的 -Sometimes, your Rust code may need to interact with code written in another -language. To do this, Rust has a keyword, `extern`, that facilitates creating +有时, 你的Rust代码需要与其它语言交互. To do this, Rust has a keyword, `extern`, that facilitates creating and using a *Foreign Function Interface* (FFI). Listing 19-8 demonstrates how to set up an integration with a function named `some_function` defined in an external library written in a language other than Rust. Functions declared From d145b7a3923a84b5f516b96a8db8e2025cb030f8 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Mon, 3 Jul 2017 11:29:57 +0800 Subject: [PATCH 53/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index c920f7a..38547b3 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -193,8 +193,7 @@ let slice = unsafe { #### 调用外部代码的`extern`函数是不安全的 -有时, 你的Rust代码需要与其它语言交互. To do this, Rust has a keyword, `extern`, that facilitates creating -and using a *Foreign Function Interface* (FFI). Listing 19-8 demonstrates how +有时, 你的Rust代码需要与其它语言交互. Rust有一个`extern`关键字可以实现这个功能, 这有助于创建并使用*外部功能接口(Foreign Function Interface)* (FFI). 例19-8 demonstrates how to set up an integration with a function named `some_function` defined in an external library written in a language other than Rust. Functions declared within `extern` blocks are always unsafe to call from Rust code: From 41d483ee5f452d6db3537f4086d50b326d169bb6 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Tue, 4 Jul 2017 13:20:41 +0800 Subject: [PATCH 54/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 38547b3..29af074 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -193,10 +193,7 @@ let slice = unsafe { #### 调用外部代码的`extern`函数是不安全的 -有时, 你的Rust代码需要与其它语言交互. Rust有一个`extern`关键字可以实现这个功能, 这有助于创建并使用*外部功能接口(Foreign Function Interface)* (FFI). 例19-8 demonstrates how -to set up an integration with a function named `some_function` defined in an -external library written in a language other than Rust. Functions declared -within `extern` blocks are always unsafe to call from Rust code: +有时, 你的Rust代码需要与其它语言交互. Rust有一个`extern`关键字可以实现这个功能, 这有助于创建并使用*外部功能接口(Foreign Function Interface)* (FFI). 例19-8演示了如何与定义在一个非Rust语言编写的外部库中的`some_function`进行交互. 在Rust中调用`extern`声明的代码块永远都是不安全的: Filename: src/main.rs @@ -210,7 +207,7 @@ fn main() { } ``` -Listing 19-8: Declaring and calling an `extern` function +例19-8: Declaring and calling an `extern` function defined in another language Within the `extern "C"` block, we list the names and signatures of functions From 60d4f84d9046d01dbc916b9f248bd4e247d408a6 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Tue, 4 Jul 2017 23:07:38 +0800 Subject: [PATCH 55/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 29af074..3784180 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -207,32 +207,17 @@ fn main() { } ``` -例19-8: Declaring and calling an `extern` function -defined in another language +例19-8: 声明并调用一个用其它语言写成的函数 -Within the `extern "C"` block, we list the names and signatures of functions -defined in a library written in another language that we want to be able to -call.`"C"` defines which *application binary interface* (ABI) the external -function uses. The ABI defines how to call the function at the assembly level. -The `"C"` ABI is the most common, and follows the C programming language's ABI. +在`extern "C"`代码块中, 我们列出了我们想调用的用其它语言实现的库中定义的函数名和这个函数的特征签名.`"C"`定义了外部函数使用了哪种*应用程序接口(application binary interface)* (ABI). ABI定义了如何在汇编层调研这个函数. `"C"`是最常用的遵循C语言的ABI. -Calling an external function is always unsafe. If we're calling into some other -language, that language does not enforce Rust's safety guarantees. Since Rust -can't check that the external code is safe, we are responsible for checking the -safety of the external code and indicating we have done so by using an `unsafe` -block to call external functions. +调用一个外部函数总是不安全的. 如果我们要调用其他语言, 这种语言却不会遵循Rust的安全保证. 因为Rust不能检查外部代码是否是安全的, 我们只负责检查外部代码的安全性来表明我们已经用`unsafe`代码块来调用外部函数了. -##### Calling Rust Functions from Other Languages +##### 通过其它语言来调用Rust函数 -The `extern` keyword is also used for creating an interface that allows other -languages to call Rust functions. Instead of an `extern` block, we can add the -`extern` keyword and specifying the ABI to use just before the `fn` keyword. We -also add the `#[no_mangle]` annotation to tell the Rust compiler not to mangle -the name of this function. The `call_from_c` function in this example would be -accessible from C code, once we've compiled to a shared library and linked from -C: +`extern`关键字也总是被用来创建一个允许被其他语言调用的Rust函数. 与`extern`代码块不同, 我们可以在`fn`关键字之前添加`extern`关键字并指定要使用的ABI. 我们也加入注解`#[no_mangle]`来告诉Rust编译器不要取笑这个函数的名字. 一旦我们把下例的代码编译成一个共享库并链接到C, 这个`call_from_c`函数就可以被C代码访问了: ```rust #[no_mangle] @@ -241,14 +226,13 @@ pub extern "C" fn call_from_c() { } ``` -This usage of `extern` does not require `unsafe` +上例的`extern`不需要`unsafe`就可以直接用 -### Accessing or Modifying a Mutable Static Variable +### 访问或修改一个可变的静态变量 -We've gone this entire book without talking about *global variables*. Many -programming languages support them, and so does Rust. However, global variables +目前为止本书还没有*讨论全局变量(global variables)*. 很多语言都支持全局变量, 当然Rust也不例外. However, global variables can be problematic: for example, if you have two threads accessing the same mutable global variable, a data race can happen. From fdc025f76fcc88b0169d3f6423e075779c922718 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Wed, 5 Jul 2017 22:46:18 +0800 Subject: [PATCH 56/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 3784180..0711f42 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -232,12 +232,9 @@ pub extern "C" fn call_from_c() { ### 访问或修改一个可变的静态变量 -目前为止本书还没有*讨论全局变量(global variables)*. 很多语言都支持全局变量, 当然Rust也不例外. However, global variables -can be problematic: for example, if you have two threads accessing the same -mutable global variable, a data race can happen. +目前为止本书还没有*讨论全局变量(global variables)*. 很多语言都支持全局变量, 当然Rust也不例外. 然而全局变量也有问题: 比如, 如果两个线程访问同一个可变的全局变量有可能会发生数据竞争. -Global variables are called *static* in Rust. Listing 19-9 shows an example -declaration and use of a static variable with a string slice as a value: +全局变量在Rust中被称为是*静态(static)*变量. 例19-9中声明并使用了一个字符串切片类型的静态变量: Filename: src/main.rs @@ -249,23 +246,11 @@ fn main() { } ``` -Listing 19-9: Defining and using an immutable static -variable +例19-9: 定义和使用一个不可变的静态变量 -`static` variables are similar to constants: their names are also in -`SCREAMING_SNAKE_CASE` by convention, and we *must* annotate the variable's -type, which is `&'static str` in this case. Only references with the `'static` -lifetime may be stored in a static variable. Because of this, the Rust compiler -can figure out the lifetime by itself and we don't need to annotate it explicitly. -Accessing immutable static variables is safe. Values in a static variable have a -fixed address in memory, and using the value will always access the same data. -Constants, on the other hand, are allowed to duplicate their data whenever they -are used. +`static`变量类似于常量: 按照惯例它们的命名遵从`SCREAMING_SNAKE_CASE(用下划线分割的全大写字母)`风格, 我们也*必须*注明变量的类型, 本例中是`&'static str`. 只有定义为`'static`的生命期才可以被存储在一个静态变量中. 也正因为此, Rust编译器自己就已经很清楚静态变量的生命期了, 所以我们也不需要明确地注明它了. 访问不可变的静态变量是安全的. 因为静态变量的值有一个固定的内存地址, 所以使用该值的时候总会得到同样的数据. 另一方面, 当常量被使用时, 复制它们的数据也是被允许的. -Another way in which static variables are different from constants is that -static variables can be mutable. Both accessing and modifying mutable static -variables is unsafe. Listing 19-10 shows how to declare, access, and modify a -mutable static variable named `COUNTER`: +静态变量与常量的另一个不同是静态变量可以是可变的. 访问和修改可变的静态变量都是不安全的. 例19-10演示了如何声明、访问和修改一个名叫`COUNTER`的可变的静态变量: Filename: src/main.rs @@ -287,12 +272,9 @@ fn main() { } ``` -Listing 19-10: Reading from or writing to a mutable -static variable is unsafe +例19-10: 读取或修改一个可变的静态变量是不安全的 -Just like with regular variables, we specify that a static variable should be -mutable using the `mut` keyword. Any time that we read or write from `COUNTER` -has to be within an `unsafe` block. This code compiles and prints `COUNTER: 3` +与常规变量一样, 我们用`mut`关键字来表明这个静态变量是可变的. 每次我们对`COUNTER`的读写都必须被放到一个`unsafe`代码块中. This code compiles and prints `COUNTER: 3` as we would expect since it's single threaded, but having multiple threads accessing `COUNTER` would likely result in data races. From 3b06ce4d771c7de64dd72cdefef34d1709b6fd6d Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Thu, 6 Jul 2017 13:12:09 +0800 Subject: [PATCH 57/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 0711f42..4eb3ecc 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -274,17 +274,11 @@ fn main() { 例19-10: 读取或修改一个可变的静态变量是不安全的 -与常规变量一样, 我们用`mut`关键字来表明这个静态变量是可变的. 每次我们对`COUNTER`的读写都必须被放到一个`unsafe`代码块中. This code compiles and prints `COUNTER: 3` -as we would expect since it's single threaded, but having multiple threads -accessing `COUNTER` would likely result in data races. +与常规变量一样, 我们用`mut`关键字来表明这个静态变量是可变的. 每次我们对`COUNTER`的读写都必须被放到一个`unsafe`代码块中. 上面的代码编译运行会打印`COUNTER: 3`, 这正如我们期望的那样, 因为程序现在是一个单线程, 如果有多个线程访问`COUNTER`就可能会导致数据竞争. -Mutable data that is globally accessible is difficult to manage and ensure that -there are no data races, which is why Rust considers mutable static variables -to be unsafe. If possible, prefer using the concurrency techniques and -threadsafe smart pointers we discussed in Chapter 16 to have the compiler check -that data accessed from different threads is done safely. +可全局访问的可变数据难于管理也很难保证没有数据竞争, 这也正是Rust认为可变的静态变量是不安全的原因. 如果可能, 请使用在第16章中介绍的并发技术和线程安全的职能指针, 这样可以让编译器从不同的线程检查被访问的数据是安全的. -### Implementing an Unsafe Trait +### 实现一个不安全的Trait Finally, the last action we're only allowed to take when we use the `unsafe` keyword is implementing an unsafe trait. We can declare that a trait is From 2b0ad8d217bc8af43d7f5776312d390106344205 Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Thu, 6 Jul 2017 13:35:39 +0800 Subject: [PATCH 58/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 4eb3ecc..4c9072a 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -276,7 +276,7 @@ fn main() { 与常规变量一样, 我们用`mut`关键字来表明这个静态变量是可变的. 每次我们对`COUNTER`的读写都必须被放到一个`unsafe`代码块中. 上面的代码编译运行会打印`COUNTER: 3`, 这正如我们期望的那样, 因为程序现在是一个单线程, 如果有多个线程访问`COUNTER`就可能会导致数据竞争. -可全局访问的可变数据难于管理也很难保证没有数据竞争, 这也正是Rust认为可变的静态变量是不安全的原因. 如果可能, 请使用在第16章中介绍的并发技术和线程安全的职能指针, 这样可以让编译器从不同的线程检查被访问的数据是安全的. +可全局访问的可变数据难于管理也很难保证没有数据竞争, 这也正是Rust认为可变的静态变量是不安全的原因. 如果可能, 请使用在第16章中介绍的并发技术和线程安全的智能指针, 这样可以让编译器从不同的线程检查被访问的数据是安全的. ### 实现一个不安全的Trait From 169cf5c240efcc1e6d387bc033cd3329b2ef653f Mon Sep 17 00:00:00 2001 From: Zheng Ping Date: Thu, 6 Jul 2017 23:06:26 +0800 Subject: [PATCH 59/59] Update ch19-01-unsafe-rust.md --- src/ch19-01-unsafe-rust.md | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index 4c9072a..57a5318 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -280,10 +280,7 @@ fn main() { ### 实现一个不安全的Trait -Finally, the last action we're only allowed to take when we use the `unsafe` -keyword is implementing an unsafe trait. We can declare that a trait is -`unsafe` by adding the `unsafe` keyword before `trait`, and then implementing -the trait must be marked as `unsafe` too, as shown in Listing 19-11: +最后, 当我们使用`unsafe`关键字时最后一个只在不安全的代码中才能做的事是实现一个不安全的trait. 我们可以在`trait`之前添加一个`unsafe`关键字来声明一个trait是不安全的, 以后实现这个trait的时候也必须标记一个`unsafe`关键字, 如19-11所示: ```rust unsafe trait Foo { @@ -295,24 +292,11 @@ unsafe impl Foo for i32 { } ``` -Listing 19-11: Defining and implementing an unsafe -trait +例19-11: 定义并实现一个不安全的trait -Like unsafe functions, methods in an unsafe trait have some invariant that the -compiler cannot verify. By using `unsafe impl`, we're promising that we'll -uphold these invariants. +与不安全的函数类似, 一个不安全的trait中的方法也有一些编译器无法验证的盲点. 通过使用`unsafe impl`, 我们就是在说明我们来保证这些有疑虑的地方的安全. -As an example, recall the `Sync` and `Send` marker traits from Chapter 16, and -that the compiler implements these automatically if our types are composed -entirely of `Send` and `Sync` types. If we implement a type that contains -something that's not `Send` or `Sync` such as raw pointers, and we want to mark -our type as `Send` or `Sync`, that requires using `unsafe`. Rust can't verify -that our type upholds the guarantees that a type can be safely sent across -threads or accessed from multiple threads, so we need to do those checks -ourselves and indicate as such with `unsafe`. +举个例子, 回想一下第16章中的`Sync`和`Send`这两个标记trait, 如果我们的类型全部由`Send`和`Sync`类型组合而成, 编译器会自动实现它们. 如果我们要实现的一个类型包含了不是`Send`或`Sync`的东西, 比如原生指针, 若是我们像把我们的类型标记成`Send`或`Sync`, 这就要求使用`unsafe`关键字. Rust不能验证我们的类型能保证可以安全地跨线程发送或从多个线程访问, 所以我们需要用`unsafe`关键字来表明我们会自己来做这些检查. -Using `unsafe` to take one of these four actions isn't wrong or frowned upon, -but it is trickier to get `unsafe` code correct since the compiler isn't able -to help uphold memory safety. When you have a reason to use `unsafe` code, -however, it's possible to do so, and having the explicit `unsafe` annotation -makes it easier to track down the source of problems if they occur. +使用`unsafe`来执行这四个动作之一是没有问题的, 因为编译器不能确保内存安全, 所以把 +`unsafe`代码写正确也实属不易. 当你需要使用`unsafe`代码时, 你可以这样做, 明确注明`unsafe`, 这样如果出现问题可以更容易地追踪问题的源头.