Merge pull request #24 from vincentsong/master

修正翻译错误并改进
pull/26/head
KaiserY 8 years ago committed by GitHub
commit e5d6416d15

@ -8,13 +8,13 @@
我们可以将每个进程运行一个程序的概念再往下抽象一层:程序也可以在其上下文中同时运行独立的部分。这个功能叫做**线程***thread*)。 我们可以将每个进程运行一个程序的概念再往下抽象一层:程序也可以在其上下文中同时运行独立的部分。这个功能叫做**线程***thread*)。
将程序需要执行的计算拆分到多个线程中可以提高性能,因为程序可以在同时进行很多工作。不过使用线程会增加程序复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这可能会由于线程以不一致的顺序访问数据或资源而导致竞争状态,或由于两个线程相互阻止对方继续运行而造成死锁,以及仅仅出现于特定场景并难以稳定重现的 bug。Rust 减少了这些或那些使用线程的负面影响,不过在多线程上下文中编程仍然需要以与只期望在单个线程中编程不同的方式思考和组织代码 将程序需要执行的计算拆分到多个线程中可以提高性能,因为程序可以在同时进行很多工作。不过使用线程会增加程序复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这可能会由于线程以不一致的顺序访问数据或资源而导致竞争状态,或由于两个线程相互阻止对方继续运行而造成死锁,以及仅仅出现于特定场景并难以稳定重现的 bug。Rust 减少了这些或那些使用线程的负面影响,不过在多线程上下文中编程,相比只期望在单个线程中运行的程序,仍然要采用不同的思考方式和代码结构
编程语言有一些不同的方法来实现线程。很多操作系统提供了创建新线程的 API。另外很多编程语言提供了自己的特殊的线程实现。编程语言提供的线程有时被称作**轻量级***lightweight*)或**绿色***green*)线程。这些语言将一系列绿色线程放入不同数量的操作系统线程中执行。因为这个原因,语言调用操作系统 API 创建线程的模型有时被称为 *1:1*,一个 OS 线程对应一个语言线程。绿色线程模型被称为 *M:N* 模型,`M`个绿色线程对应`N`个 OS 线程,这里`M`和`N`不必相同。 编程语言有一些不同的方法来实现线程。很多操作系统提供了创建新线程的 API。另外很多编程语言提供了自己的特殊的线程实现。编程语言提供的线程有时被称作**轻量级***lightweight*)或**绿色***green*)线程。这些语言将一系列绿色线程放入不同数量的操作系统线程中执行。因为这个原因,语言调用操作系统 API 创建线程的模型有时被称为 *1:1*,一个 OS 线程对应一个语言线程。绿色线程模型被称为 *M:N* 模型,`M`个绿色线程对应`N`个 OS 线程,这里`M`和`N`不必相同。
每一个模型都有其自己的优势和取舍。对于 Rust 来说最重要的取舍是运行时支持。**运行时**是一个令人迷惑的概念;在不同上下文中它可能有不同的含义。这里其代表二进制文件中包含的语言自身的代码。对于一些语言,这些代码是庞大的,另一些则很小。通俗的说,“没有运行时”通常被人们用来指代“小运行时”,因为任何非汇编语言都存在一定数量的运行时。更小的运行时拥有更少的功能不过其优势在于更小的二进制输出。更小的二进制文件更容易在更多上下文中与其他语言结合。虽然很多语言觉得增加运行时来换取更多功能没有什么问题,但是 Rust 需要做到几乎没有运行时,同时不能在为了维持性能而能够在 C 语言中调用方面做出妥协 每一个模型都有其自己的优势和取舍。对于 Rust 来说最重要的取舍是运行时支持。**运行时**是一个令人迷惑的概念;在不同上下文中它可能有不同的含义。这里其代表二进制文件中包含的语言自身的代码。对于一些语言,这些代码是庞大的,另一些则很小。通俗的说,“没有运行时”通常被人们用来指代“小运行时”,因为任何非汇编语言都存在一定数量的运行时。更小的运行时拥有更少的功能不过其优势在于更小的二进制输出。更小的二进制文件更容易在更多上下文中与其他语言结合。虽然很多语言觉得增加运行时来换取更多功能没有什么问题,但是 Rust 需要做到几乎没有运行时,同时为了保持高性能必需能够调用 C 语言,这点也是不能妥协的
绿色线程模型功能要求更大的运行时来管理这些线程。为此Rust 标准库只提供了 1:1 线程模型实现。因为 Rust 是这么一个底层语言,所以有相应的 crate 实现了 M:N 线程模型,如果你宁愿牺牲性能来换取例如更多的线程控制和更低的上下文切换消耗 绿色线程模型功能要求更大的运行时来管理这些线程。为此Rust 标准库只提供了 1:1 线程模型实现。因为 Rust 是这么一个底层语言,所以有相应的 crate 实现了 M:N 线程模型,如果你宁愿牺牲性能来换取例如更好的线程运行控制和更低的上下文切换成本
现在我们明白了 Rust 中的线程是如何定义的,让我们开始探索如何使用标准库提供的线程相关的 API吧。 现在我们明白了 Rust 中的线程是如何定义的,让我们开始探索如何使用标准库提供的线程相关的 API吧。

@ -144,7 +144,7 @@ error[E0382]: use of moved value: `val`
### 发送多个值并观察接收者的等待 ### 发送多个值并观察接收者的等待
列表 16-8 中的代码可以编译和运行,不过这并不是很有趣:通过它难以看出两个独立的线程在一个通道上相互通讯。列表 16-10 则有一些改进会证明这些代码是并发执行的:新建线程现在会发送多个消息并在每个消息之间暂一段时间。 列表 16-8 中的代码可以编译和运行,不过这并不是很有趣:通过它难以看出两个独立的线程在一个通道上相互通讯。列表 16-10 则有一些改进会证明这些代码是并发执行的:新建线程现在会发送多个消息并在每个消息之间暂一段时间。
<span class="filename">Filename: src/main.rs</span> <span class="filename">Filename: src/main.rs</span>

@ -22,7 +22,7 @@
1. 必须记住在使用数据之前尝试获取锁。 1. 必须记住在使用数据之前尝试获取锁。
2. 一旦处理完被互斥器所保护的数据之后,必须记得解锁数据这样其他线程才能够获取锁。 2. 一旦处理完被互斥器所保护的数据之后,必须记得解锁数据这样其他线程才能够获取锁。
对于一个现实中的互斥器的例子,想象一下在一个会议中的专门小组讨论会上,不过只有一个麦克风。在一个小组成员可能发言之前,他们必须请求或示意他们需要使用麦克风。一旦得到了麦克风,他们可以发言任意长的时间,接着将麦克风交给一个希望讲话的小组成员。如果小组成员在没有麦克风的时候就开始叫喊或者在其他成员发言结束之前就取得麦克风将是很无理的。如果对这个共享的麦克风的管理因为任何这些原因出现问题,讨论会将无法如期进行。 对于一个现实中的互斥器的例子,想象一下在一个会议中的专门小组讨论会上,不过只有一个麦克风。在一个小组成员可能发言之前,他们必须请求或示意他们需要使用麦克风。一旦得到了麦克风,他们可以发言任意长的时间,接着将麦克风交给一个希望讲话的小组成员。如果小组成员在没有麦克风的时候就开始叫喊或者在其他成员发言结束之前就取得麦克风将是很无理的。如果对这个共享的麦克风的管理因为任何这些原因出现问题,讨论会将无法如期进行。
正确的管理互斥器是异常复杂的,这也就是为什么这么多人都热衷于通道。然而,在 Rust 中,得益于类型系统和所有权,我们不可能会在锁和解锁上出错。 正确的管理互斥器是异常复杂的,这也就是为什么这么多人都热衷于通道。然而,在 Rust 中,得益于类型系统和所有权,我们不可能会在锁和解锁上出错。
@ -50,9 +50,9 @@ fn main() {
<span class="caption">Listing 16-12: Exploring the API of `Mutex<T>` in a <span class="caption">Listing 16-12: Exploring the API of `Mutex<T>` in a
single threaded context for simplicity</span> single threaded context for simplicity</span>
与很多类型一样,我们通过叫做`new`的关联函数来创建一个`Mutex<T>`。为了访问互斥器中的数据,使用`lock`方法来获取锁。这个调用会阻塞到直到轮到我们拥有锁为止。如果另一个线程拥有锁接着那个线程 panic 了则这个调用会失败。类似于上一部分列表 16-6 那样,我们暂时使用`unwrap()`而不是更好的错误处理。请查看第九章中提供的更好的工具。 与很多类型一样,我们通过叫做`new`的关联函数来创建一个`Mutex<T>`。为了访问互斥器中的数据,使用`lock`方法来获取锁。这个调用会阻塞到直到轮到我们拥有锁为止。如果另一个线程拥有锁接着那个线程 panic 了则这个调用会失败。类似于上一部分列表 16-6 那样,我们暂时使用`unwrap()`,至于更好的错误处理,请查看第九章中提供的更好的工具。
一旦获取了锁,就可以将返回值(在这里是`num`)作为一个数据的可变引用使用了。类型系统是 Rust 如何保证使用值之前必须获取锁的:`Mutex<i32>`并不是一个`i32`,所以**必须**获取锁才能使用这个`i32`值。我们是不会忘记这么做的;类否则型系统是不会允许的。 一旦获取了锁,就可以将返回值(在这里是`num`)作为一个数据的可变引用使用了。类型系统是 Rust 如何保证使用值之前必须获取锁的:`Mutex<i32>`并不是一个`i32`,所以**必须**获取锁才能使用这个`i32`值。我们是不会忘记这么做的;因为类型系统是不会允许的。
与你可能怀疑的一样,`Mutex<T>`是一个智能指针。好吧,更准确的说,`lock`调用返回一个叫做`MutexGuard`的智能指针。类似我们在第十五章见过的智能指针,它实现了`Deref`来指向其内部数据。另外`MutexGuard`有一个用来释放锁的`Drop`实现。这样就不会忘记释放锁了。这在`MutexGuard`离开作用域时会自动发生,例如它发生于列表 16-12 中内部作用域的结尾。接着可以打印出互斥器的值并发现能够将其内部的`i32`改为 6。 与你可能怀疑的一样,`Mutex<T>`是一个智能指针。好吧,更准确的说,`lock`调用返回一个叫做`MutexGuard`的智能指针。类似我们在第十五章见过的智能指针,它实现了`Deref`来指向其内部数据。另外`MutexGuard`有一个用来释放锁的`Drop`实现。这样就不会忘记释放锁了。这在`MutexGuard`离开作用域时会自动发生,例如它发生于列表 16-12 中内部作用域的结尾。接着可以打印出互斥器的值并发现能够将其内部的`i32`改为 6。
@ -197,7 +197,7 @@ error[E0382]: use of moved value: `counter`
error: aborting due to 2 previous errors error: aborting due to 2 previous errors
``` ```
啊哈在第一个错误信息中Rust 表明了`counter`被移动进了`handle`所代表线程的闭包中。这个移动阻止我们在对其调用`lock`并将结果储存在`num2`中时捕获`counter`,这是已经在第二个线程中了!所以 Rust 告诉我们不能将`counter`的所有权移动到多个线程中。这在之前很难看出是因为我们在循环中创建多个线程,而 Rust 无法在循环的迭代中指明不同的线程(没有临时变量)。 啊哈在第一个错误信息中Rust 表明了`counter`被移动进了`handle`所代表线程的闭包中。这个移动阻止我们在第二个线程中对其调用`lock`并将结果储存在`num2`中时捕获`counter`!所以 Rust 告诉我们不能将`counter`的所有权移动到多个线程中。这在之前很难看出是因为我们在循环中创建多个线程,而 Rust 无法在循环的迭代中指明不同的线程(没有临时变量`num2`)。
#### 多线程和多所有权 #### 多线程和多所有权
@ -254,15 +254,15 @@ std::marker::Send` is not satisfied
= note: required by `std::thread::spawn` = note: required by `std::thread::spawn`
``` ```
哇哦,太长不看!需要指出一些重要的部分:第一个提示表明`Rc<Mutex<i32>>`不能安全的在线程间传递。理由也在错误信息中,经过提取之后,表明“不满足`Send` trait bound”`the trait bound Send is not satisfied`)。下一部分将会讨论`Send`,它是一个确保确保用于线程的类型是适合并发环境的 trait 哇哦,太长不看!需要指出一些重要的部分:第一个提示表明`Rc<Mutex<i32>>`不能安全的在线程间传递。理由也在错误信息中,经过提取之后,表明“不满足`Send` trait bound”`the trait bound Send is not satisfied`)。下一部分将会讨论`Send`,它是许多确保用在多线程中的类型能够适合并发环境的 trait 之一
不幸的是,`Rc<T>`并不能安全的在线程间共享。当`Rc<T>`管理引用计数时,它必须在每一个`clone`调用时增加计数并在每一个克隆被丢弃时减少计数。`Rc<T>`并没有使用任何并发原语来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致诡异的 bug比如可能会造成内存泄漏或在使用结束之前就丢弃一个值。那么如果有一个正好与`Rc<T>`类似,不过以一种线程安全的方式改变引用计数的类型会怎么样呢? 不幸的是,`Rc<T>`并不能安全的在线程间共享。当`Rc<T>`管理引用计数时,它必须在每一个`clone`调用时增加计数并在每一个克隆被丢弃时减少计数。`Rc<T>`并没有使用任何并发原语来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致诡异的 bug比如可能会造成内存泄漏或在使用结束之前就丢弃一个值。那么如果有一个正好与`Rc<T>`类似,不过以一种线程安全的方式改变引用计数的类型会怎么样呢?
#### 原子引用计数`Arc<T>` #### 原子引用计数`Arc<T>`
如果你思考过像之前那样的问题的话,你就是正确的。确实有一个类似`Rc<T>`并可以安全的用于并发环境的类型:`Arc<T>`。字母“a”代表**原子性***atomic*),所以这是一个**原子引用计数***atomically reference counted*)类型。原子性是另一类这里还未涉及到的并发原语;请查看标准库中`std::sync::atomic`的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。 如果你思考过像之前那样的问题的话,你就是正确的。确实有一个类似`Rc<T>`并可以安全的用于并发环境的类型:`Arc<T>`。字母“a”代表**原子性***atomic*),所以这是一个**原子引用计数***atomically reference counted*)类型。原子性是另一类这里还未涉及到的并发原语;请查看标准库中`std::sync::atomic`的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。
那为什么不是所有的原始类型都是原子性的呢,然后为什么不是所有标准库中的类型都默认使用`Arc<T>`实现呢?线程安全伴随一些性能惩罚,我们只希望在需要时才为此付出代价。如果只是在单线程中值进行操作,因为并不需要原子性提供的保证代码可以运行的更快。 那为什么不是所有的原始类型都是原子性的呢,然后为什么不是所有标准库中的类型都默认使用`Arc<T>`实现呢?线程安全伴随一些性能惩罚,我们只希望在需要时才为此付出代价。如果只是在单线程中值进行操作,因为并不需要原子性提供的保证,所以代码可以运行的更快。
回到之前的例子:`Arc<T>`和`Rc<T>`除了`Arc<T>`内部的原子性之外他们是等价的。其 API 也是一样的,所以可以修改`use`行和`new`调用。列表 16-15 中的代码最终可以编译和运行: 回到之前的例子:`Arc<T>`和`Rc<T>`除了`Arc<T>`内部的原子性之外他们是等价的。其 API 也是一样的,所以可以修改`use`行和`new`调用。列表 16-15 中的代码最终可以编译和运行:
@ -303,12 +303,12 @@ to be able to share ownership across multiple threads</span>
Result: 10 Result: 10
``` ```
成功了!我们从 0 数到了 10这可能并不是很显眼不过一路上我们学习了很多关于`Mutex<T>`和线程安全的内容!这个例子中构建的结构可以用于比增加计数更为复杂的操作。可以被分解为独立部分的计算可以像这样被分散到多个线程中,并可以使用`Mutex<T>`来允许每个线程在他们自己的部分更新最终的结果。 成功了!我们从 0 数到了 10这可能并不是很显眼不过一路上我们学习了很多关于`Mutex<T>`和线程安全的内容!这个例子中构建的结构可以用于比增加计数更为复杂的操作。能够被分解为独立部分的计算可以像这样被分散到多个线程中,并可以使用`Mutex<T>`来允许每个线程在他们自己的部分更新最终的结果。
你可能注意到了,因为`counter`是不可变的,不过可以获取其内部值的可变引用,这意味着`Mutex<T>`提供了内部可变性,就像`Cell`系列类型那样。正如第十五章中使用`RefCell<T>`可以改变`Rc<T>`中的内容那样,同样的可以使用`Mutex<T>`来改变`Arc<T>`中的内容。 你可能注意到了,因为`counter`是不可变的,不过可以获取其内部值的可变引用,这意味着`Mutex<T>`提供了内部可变性,就像`Cell`系列类型那样。正如第十五章中使用`RefCell<T>`可以改变`Rc<T>`中的内容那样,同样的可以使用`Mutex<T>`来改变`Arc<T>`中的内容。
回忆一下`Rc<T>`并没有避免所有可能的问题:我们也讨论了当两个`Rc<T>`相互引用时的引用循环的可能性,这可能造成内存泄露。`Mutex<T>`有一个类似的 Rust 同样也不能避免的问题:死锁。**死锁***deadlock*)是一个场景中操作需要锁定两个资源,而两个线程分别拥有一个锁并永远相互等待的问题。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中`Mutex<T>`和`MutexGuard`的 API 文档会提供有的信息。 回忆一下`Rc<T>`并没有避免所有可能的问题:我们也讨论了当两个`Rc<T>`相互引用时的引用循环的可能性,这可能造成内存泄露。`Mutex<T>`有一个类似的 Rust 同样也不能避免的问题:死锁。**死锁***deadlock*)是一个场景中操作需要锁定两个资源,而两个线程分别拥有一个锁并永远相互等待的问题。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中`Mutex<T>`和`MutexGuard`的 API 文档会提供有的信息。
Rust 的类型系统和所有权确保了线程在更新共享值时拥有独占的访问权限,所以线程不会以一种不可预测的方式覆盖彼此的操作。为了和编译器一起使一切正确运行花了一些时间,不过我们节省了未来可能需要重现只在线程以特定顺序执行才会出现的诡异错误场景的时间。 Rust 的类型系统和所有权(规则)确保了线程在更新共享值时拥有独占的访问权限,所以线程不会以一种不可预测的方式覆盖彼此的操作。虽然为了使一切正确运行而在编译器上花了一些时间,但是我们节省了未来可能需要重现只在线程以特定顺序执行才会出现的诡异错误场景的时间。
接下来,为了丰富本章的内容,让我们讨论一下`Send`和`Sync` trait 以及如何对自定义类型使用他们。 接下来,为了丰富本章的内容,让我们讨论一下`Send`和`Sync` trait 以及如何对自定义类型使用他们。

@ -6,7 +6,7 @@
Rust 的并发模型中一个有趣的方面是语言本身对并发知道的**很少**。我们讨论过的几乎所有内容都是标准库的一部分,而不是语言本身的内容。因为并不需要语言提供任何用于并发上下文中的内容,并发选择也不仅限于标准库或语言所提供的:我们可以编写自己的或使用别人编写的内容。 Rust 的并发模型中一个有趣的方面是语言本身对并发知道的**很少**。我们讨论过的几乎所有内容都是标准库的一部分,而不是语言本身的内容。因为并不需要语言提供任何用于并发上下文中的内容,并发选择也不仅限于标准库或语言所提供的:我们可以编写自己的或使用别人编写的内容。
我们说了**几乎**所有内容都不在语言本身,那么位于语言本身的是什么呢?这是两个 trait都位于`std::marker``Sync`和`Send`。 我们说了**几乎**所有内容都不在语言本身,那么位于语言本身的是什么呢?这是两个 trait都位于`std::marker`:`Sync`和`Send`。
### `Send`用于表明所有权可能被传送给其他线程 ### `Send`用于表明所有权可能被传送给其他线程
@ -24,9 +24,9 @@ Rust 的并发模型中一个有趣的方面是语言本身对并发知道的**
### 手动实现`Send`和`Sync`是不安全的 ### 手动实现`Send`和`Sync`是不安全的
通常并不需要实现`Send`和`Sync` trait因为由是`Send`和`Sync`的类型组成的类型也自动就是`Send`和`Sync`的了。因为他们是标记 trait甚至都不需要实现任何方法。他们只是用来加强并发相关的不可变性的。 通常并不需要实现`Send`和`Sync` trait因为由是`Send`和`Sync`的类型组成的类型也自动就是`Send`和`Sync`的了。因为他们是标记 trait甚至都不需要实现任何方法。他们只是用来加强并发相关的不可变性的。
实现这些标记 trait 涉及到实现不安全的 Rust 代码。第十九章将会讲到如何使用不安全 Rust 代码;现在,重要的是在创建新的由不是`Send`和`Sync`的部分构成的并发类型时需要多加小心,以确保维持其安全保证。[The Nomicon] 中由更多关于这些保证和如何维持他们的信息。 实现这些标记 trait 涉及到实现不安全的 Rust 代码。第十九章将会讲到如何使用不安全 Rust 代码;现在,重要的是在创建新的由不是`Send`和`Sync`的部分构成的并发类型时需要多加小心,以确保维持其安全保证。[The Nomicon] 中有更多关于这些保证以及如何维持他们的信息。
[The Nomicon]: https://doc.rust-lang.org/stable/nomicon/vec.html [The Nomicon]: https://doc.rust-lang.org/stable/nomicon/vec.html
@ -36,6 +36,6 @@ Rust 的并发模型中一个有趣的方面是语言本身对并发知道的**
正如我们提到的,因为 Rust 本身很少有处理并发的部分内容,有很多的并发方案都由 crate 实现。他们比标准库要发展的更快;请在网上搜索当前最新的用于多线程场景的 crate。 正如我们提到的,因为 Rust 本身很少有处理并发的部分内容,有很多的并发方案都由 crate 实现。他们比标准库要发展的更快;请在网上搜索当前最新的用于多线程场景的 crate。
Rust 提供了用于消息传递的通道,和像`Mutex<T>`和`Arc<T>`这样可以安全的用于并发上下文的智能指针。类型系统和借用检查器会确保这些场景中的代码不会出现数据竞争和无效的引用。一旦代码可以编译了,我们就可以坚信这些代码可以正确的运行于多线程环境,而不会出现其他语言中经常出现的那些难以追踪的 bug。并发编程不再是什么可怕的概念无所畏惧的使你的程序使用并发吧! Rust 提供了用于消息传递的通道,和像`Mutex<T>`和`Arc<T>`这样可以安全的用于并发上下文的智能指针。类型系统和借用检查器会确保这些场景中的代码不会出现数据竞争和无效的引用。一旦代码可以编译了,我们就可以坚信这些代码可以正确的运行于多线程环境,而不会出现其他语言中经常出现的那些难以追踪的 bug。并发编程不再是什么可怕的概念无所畏惧地让你的程序使用并发吧!
接下来,让我们讨论一下当 Rust 程序变得更大时那些符合习惯的模拟问题和结构的解决方案,以及 Rust 风格如何与面向对象编程Object Oriented Programming中那些你所熟悉的概念相联系。 接下来,让我们讨论一下当 Rust 程序变得更大时有哪些符合语言习惯的问题建模和节构化解决方案的做法,以及 Rust 的风格是如何与面向对象编程Object Oriented Programming中那些你所熟悉的概念相联系

@ -4,4 +4,4 @@
> <br> > <br>
> commit 759801361bde74b47e81755fff545c66020e6e63 > commit 759801361bde74b47e81755fff545c66020e6e63
面向对象编程Object-Oriented Programming是一种起源于 20 世纪 60 年代的 Simula 编程语言的模式化编程方式,然后在 90 年代随着 C++ 语言开始流行。为了描述 OOP 有很多种复杂的定义在一些定义下Rust 是面向对象的在其他定义下Rust 不是。在本章节中,我们会探索一些被普遍认为是面向对象的特性和这些特性是如何转换为 Rust 方言的。 面向对象编程Object-Oriented Programming是一种起源于 20 世纪 60 年代的 Simula 编程语言的模式化编程方式,然后在 90 年代随着 C++ 语言开始流行。为了描述 OOP 有很多种复杂的定义在一些定义下Rust 是面向对象的在其他定义下Rust 不是。在本章节中,我们会探索一些被普遍认为是面向对象的特性和这些特性是如何体现在 Rust 语言习惯中的。

@ -4,7 +4,7 @@
> <br> > <br>
> commit 2a9b2a1b019ad6d4832ff3e56fbcba5be68b250e > commit 2a9b2a1b019ad6d4832ff3e56fbcba5be68b250e
关于一门语言是否需要是面向对象在编程社区内并未达成一致意见。Rust 被很多不同的编程式影响,我们探索了十三章提到的函数式编程的特性。面向对象编程语言的一些特性往往是对象、封装和继承。我们看一下这每一个概念的含义以及 Rust 是否支持他们。 关于一门语言是否需要是面向对象在编程社区内并未达成一致意见。Rust 被很多不同的编程式影响,我们探索了十三章提到的函数式编程的特性。面向对象编程语言的一些特性往往是对象、封装和继承。我们看一下这每一个概念的含义以及 Rust 是否支持他们。
## 对象包含数据和行为 ## 对象包含数据和行为
@ -14,7 +14,7 @@
> data and the procedures that operate on that data. The procedures are > data and the procedures that operate on that data. The procedures are
> typically called *methods* or *operations*. > typically called *methods* or *operations*.
> >
> 面向对象的程序是由对象组成的。一个**对象**包数据和操作这些数据的过程。这些过程通常被称为**方法**或**操作**。 > 面向对象的程序是由对象组成的。一个**对象**包数据和操作这些数据的过程。这些过程通常被称为**方法**或**操作**。
在这个定义下Rust 是面向对象的:结构体和枚举包含数据而 impl 块提供了在结构体和枚举之上的方法。虽然带有方法的结构体和枚举并不被**称为**对象,但是他们提供了与对象相同的功能,参考 Gang of Four 所定义的对象。 在这个定义下Rust 是面向对象的:结构体和枚举包含数据而 impl 块提供了在结构体和枚举之上的方法。虽然带有方法的结构体和枚举并不被**称为**对象,但是他们提供了与对象相同的功能,参考 Gang of Four 所定义的对象。
@ -96,8 +96,8 @@ impl AveragedCollection {
<!-- PROD: END BOX --> <!-- PROD: END BOX -->
为了支持这种模式Rust 有 **trait 对象***trait objects*),这样我们可以指定给任何类型的值,只要值实现了一种特定的 trait。 为了支持这种模式Rust 有 **trait 对象***trait objects*),这样我们也可以指明接受任意类型的值,只要这个值实现了一种特定的 trait。
继承最近在很多编程语言的设计方案中失宠了。使用继承来实现代码重用需要共享比你需要共享的代码。子类不应该总是共享它们的父类的所有特色,但是继承意味着子类得到了它父类所有的数据和行为。这使得程序的设计更加不灵活,并产生了无意义的方法调用或子类,或者由于方法并不适用于子类不过必需从父类继承而造成错误的可能性。另外,一些语言只允许子类继承一个父类,这进一步限制了程序设计的灵活性。 继承最近在很多编程语言的设计方案中失宠了。使用继承来实现代码重用,会共享比实际需要更多的代码。子类不应该总是共享它们的父类的所有特性,但是继承意味着子类得到了它父类所有的数据和行为。这使得程序的设计更加不灵活,并产生了无意义的方法调用或子类,或者由于方法并不适用于子类,但必需从父类继承而造成错误的可能性。另外,一些语言只允许子类继承一个父类,这进一步限制了程序设计的灵活性。
因为这些原因Rust 选择了一个另外的途径,使用 trait 对象替代继承。让我们看一下在 Rust 中 trait 对象是如何实现多态的。 因为这些原因Rust 选择了一个另外的途径,使用 trait 对象替代继承。让我们看一下在 Rust 中 trait 对象是如何实现多态的。

@ -20,9 +20,9 @@ get Chapter 8 for editing. /Carol -->
不过在Rust语言中我们可以定义一个名为`Draw`的trait其上有一个名为`draw`的方法。我们定义一个带有*trait对象*的vector绑定了一种指针的trait比如`&`引用或者一个`Box<T>`智能指针。 不过在Rust语言中我们可以定义一个名为`Draw`的trait其上有一个名为`draw`的方法。我们定义一个带有*trait对象*的vector绑定了一种指针的trait比如`&`引用或者一个`Box<T>`智能指针。
我们提到,我们不会调用结构体和枚举的对象,从而区分于其他语言的对象。在结构体的数据或者枚举的字段和`impl`块中的行为是分开的而其他语言则是数据和行为被组合到一个概念里。Trait对象更像其他语言的对象在这种场景下他们组合了由指针组成的数据到实体对象该对象带有在trait中定义的方法行为。但是trait对象是和其他语言是不同的因为我们不能向一个trait对象增加数据。trait对象不像其他语言那样有用它们的目的是允许从公有的行为上抽象。 我们提到,我们不会称结构体和枚举为对象,这是为了区分于其他语言的结构体和枚举对象。结构体或者枚举成员中的数据和`impl`块中的行为是分开的,而其他语言则是数据和行为被组合到一个被称作对象的概念里。Trait对象更像其他语言的对象之所以这样说是因为他们把由其指针所指向的具体对象作为数据把在trait中定义的方法作为行为组合在了一起。但是trait对象和其他语言是不同的因为我们不能向一个trait对象增加数据。trait对象不像其他语言那样有用它们的目的是允许从公有的行为上抽象。
trait定义了在给定场景下我们所需要的行为。在我们会使用一个实体类型或者一个通用类型的地方我们可以把trait当作trait对象使用。Rust的类型系统会保证我们为trait对象带入的任何值会实现trait的方法。我们不需要在编译阶段知道所有可能的类型我们可以把所有的实例统一对待。Listing 17-03展示了如何定义一个名为`Draw`的带有`draw`方法的trait。 trait定义了在给定情况下我们所需要的行为。在我们需要使用一个实体类型或者一个通用类型的地方我们可以把trait当作trait对象使用。Rust的类型系统会保证我们为trait对象带入的任何值会实现trait的方法。我们不需要在编译阶段知道所有可能的类型我们可以把所有的实例统一对待。Listing 17-03展示了如何定义一个名为`Draw`的带有`draw`方法的trait。
<span class="filename">Filename: src/lib.rs</span> <span class="filename">Filename: src/lib.rs</span>
@ -36,7 +36,7 @@ pub trait Draw {
<!-- NEXT PARAGRAPH WRAPPED WEIRD INTENTIONALLY SEE #199 --> <!-- NEXT PARAGRAPH WRAPPED WEIRD INTENTIONALLY SEE #199 -->
因为我们已经在第10章讨论过如何定义trait你可能比较熟悉。下面是新的定义Listing 17-4有一个名为`Screen`的结构体,里面有一个名为`components`的vector`components`的类型是Box<Draw>。`Box<Draw>`是一个trait对象它是一个任何`Box`内部的实现了`Draw`trait的类型的替身。 因为我们已经在第10章讨论过如何定义trait你可能比较熟悉。下面是新的定义Listing 17-4有一个名为`Screen`的结构体,里面有一个名为`components`的vector`components`的类型是Box<Draw>。`Box<Draw>`是一个trait对象它是`Box`内部任意一个实现了`Draw`trait的类型的替身。
<span class="filename">Filename: src/lib.rs</span> <span class="filename">Filename: src/lib.rs</span>
@ -79,7 +79,7 @@ impl Screen {
<span class="caption">Listing 17-5:在`Screen`上实现一个`run`方法,该方法在每个组件上调用`draw`方法 <span class="caption">Listing 17-5:在`Screen`上实现一个`run`方法,该方法在每个组件上调用`draw`方法
</span> </span>
是区别于定义一个使用带有trait绑定的通用类型参数的结构体。通用类型参数一次只能被一个实体类型替代而trait对象可以在运行时允许多种实体类型填充trait对象。比如我们已经定义了`Screen`结构体使用通用类型和一个trait绑如Listing 17-6所示 不同于定义一个使用带有trait限定的泛型参数的结构体。泛型参数一次只能被一个实体类型替代而trait对象可以在运行时允许多种实体类型填充trait对象。比如我们已经定义了`Screen`结构体使用泛型和一个trait限如Listing 17-6所示
<span class="filename">Filename: src/lib.rs</span> <span class="filename">Filename: src/lib.rs</span>
@ -105,7 +105,7 @@ impl<T> Screen<T>
<span class="caption">Listing 17-6: 一种`Screen`结构体的替代实现,它的`run`方法使用通用类型和trait绑定 <span class="caption">Listing 17-6: 一种`Screen`结构体的替代实现,它的`run`方法使用通用类型和trait绑定
</span> </span>
这个例子只能使我们有一个`Screen`实例,这个实例有一个组件列表,所有的组件类型是`Button`或者`TextField`。如果你有同种的集合那么可以优先使用通用和trait绑定这是因为为了使用具体的类型定义是在编译阶段是单一的。 这个例子只能使我们的`Screen`实例的所有组件类型全是`Button`,或者全是`TextField`。如果你的组件集合是单一类型的那么可以优先使用泛型和trait限定这是因为其使用的具体类型在编译阶段可以被定意为是单一的。
而如果使用内部有`Vec<Box<Draw>>` trait对象的列表的`Screen`结构体,`Screen`实例可以同时包含`Box<Button>`和`Box<TextField>`的`Vec`。我们看它是怎么工作的,然后讨论运行时性能的实现。 而如果使用内部有`Vec<Box<Draw>>` trait对象的列表的`Screen`结构体,`Screen`实例可以同时包含`Box<Button>`和`Box<TextField>`的`Vec`。我们看它是怎么工作的,然后讨论运行时性能的实现。

Loading…
Cancel
Save