From 201565cf20faef254af833b9c02e41f951483796 Mon Sep 17 00:00:00 2001 From: murphy Date: Thu, 11 May 2017 23:51:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=AC=94=E8=AF=AF=E5=92=8C?= =?UTF-8?q?=E8=A1=A8=E8=BF=B0=20=09modified:=20=20=20src/ch04-01-what-is-o?= =?UTF-8?q?wnership.md=20=09modified:=20=20=20src/ch15-01-box.md=20=09modi?= =?UTF-8?q?fied:=20=20=20src/ch16-03-shared-state.md=20=09modified:=20=20?= =?UTF-8?q?=20src/ch17-01-what-is-oo.md=20=09modified:=20=20=20src/ch17-02?= =?UTF-8?q?-trait-objects.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ch04-01-what-is-ownership.md | 12 ++++++------ src/ch15-01-box.md | 2 +- src/ch16-03-shared-state.md | 12 ++++++------ src/ch17-01-what-is-oo.md | 4 ++-- src/ch17-02-trait-objects.md | 10 +++++----- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ch04-01-what-is-ownership.md b/src/ch04-01-what-is-ownership.md index b08545a..3a817a7 100644 --- a/src/ch04-01-what-is-ownership.md +++ b/src/ch04-01-what-is-ownership.md @@ -25,22 +25,22 @@ Rust 的核心功能(之一)是**所有权**(*ownership*)。虽然这个 > 相反对于在编译时未知大小或大小可能变化的数据,可以把他们储存在堆上。堆是缺乏组织的:当向堆放入数据时,我们请求一定大小的空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回给我们一个它位置的指针。这个过程称作**在堆上分配内存**(*allocating on the heap*),并且有时这个过程就简称为“分配”(allocating)。向栈中放入数据并不被认为是分配。因为指针是已知的固定大小的,我们可以将指针储存在栈上,不过当需要实际数据时,必须访问指针。 > > 想象一下去餐馆就坐吃饭。当进入时,你说明有几个人,餐馆员工会找到一个够大的空桌子并领你们过去。如果有人来迟了,他们也可以通过询问来找到你们坐在哪。 -> +> > 访问堆上的数据要比访问栈上的数据要慢因为必须通过指针来访问。现代的处理器在内存中跳转越少就越快。继续类比,假设有一台服务器来处理来自多个桌子的订单。它在处理完一个桌子的所有订单后再移动到下一个桌子是最有效率的。从桌子 A 获取一个订单,接着再从桌子 B 获取一个订单,然后再从桌子 A,然后再从桌子 B 这样的流程会更加缓慢。出于同样原因,处理器在处理的数据之间彼此较近的时候(比如在栈上)比较远的时候(比如可能在堆上)能更好的工作。在堆上分配大量的空间也可能消耗时间。 > > 当调用一个函数,传递给函数的值(包括可能指向堆上数据的指针)和函数的局部变量被压入栈中。当函数结束时,这些值被移出栈。 -> +> > 记录何处的代码在使用堆上的什么数据,最小化堆上的冗余数据的数量以及清理堆上不再使用的数据以致不至于耗尽空间,这些所有的问题正是所有权系统要处理的。一旦理解了所有权,你就不需要经常考虑栈和堆了,不过理解如何管理堆内存可以帮助我们理解所有权为何存在以及为什么以这种方式工作。 ### 所有权规则 -首先,让我们看一下所有权的规则。请记住这些规则因为我们将讲解一些说明这些规则的例子: +首先,让我们看一下所有权的规则。请记住它们,我们将讲解一些它们的例子: -> 1. Rust 中的每一个值都有一个叫做它的**所有者**(*owner*)的变量。 -> 2. 同时一次只能有一个所有者 -> 3. 当所有者变量离开作用域,这个值将被丢弃。 +> 1. 每一个值都被它的**所有者**(*owner*)变量拥有。 +> 2. 值在任意时刻只能被一个所有者拥有。 +> 3. 当所有者离开作用域,这个值将被丢弃。 ### 变量作用域 diff --git a/src/ch15-01-box.md b/src/ch15-01-box.md index e48ec24..ecdb719 100644 --- a/src/ch15-01-box.md +++ b/src/ch15-01-box.md @@ -4,7 +4,7 @@ >
> commit 85b2c9ac704c9dc4bbedb97209d336afb9809dc1 -最简单直接的智能指针是 *box*,它的类型是`Box`。 box 允许你将一个单独的值放在堆上(第四章介绍或栈与堆)。列表 15-1 展示了如何使用 box 在堆上储存一个`i32`: +最简单直接的智能指针是 *box*,它的类型是`Box`。 box 允许你将一个值放在堆上(第四章介绍过栈与堆)。列表 15-1 展示了如何使用 box 在堆上储存一个`i32`: Filename: src/main.rs diff --git a/src/ch16-03-shared-state.md b/src/ch16-03-shared-state.md index 7683dbb..b050e0c 100644 --- a/src/ch16-03-shared-state.md +++ b/src/ch16-03-shared-state.md @@ -207,7 +207,7 @@ error: aborting due to 2 previous errors Filename: src/main.rs -```rust,ignore +```rust use std::rc::Rc; use std::sync::Mutex; use std::thread; @@ -258,15 +258,15 @@ std::marker::Send` is not satisfied 哇哦,太长不看!说重点:第一个提示表明 `Rc>` 不能安全的在线程间传递。理由也在错误信息中,“不满足 `Send` trait bound”(`the trait bound Send is not satisfied`)。下一部分将会讨论 `Send`,它是确保许多用在多线程中的类型,能够适合并发环境的 trait 之一。 -不幸的是,`Rc` 并不能安全的在线程间共享。当 `Rc` 管理引用计数时,它必须在每一个 `clone` 调用时增加计数,并在每一个克隆被丢弃时减少计数。`Rc` 并没有使用任何并发原语,来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致诡异的 bug,比如可能会造成内存泄漏,或在使用结束之前就丢弃一个值。那么如果有一个正好与 `Rc` 类似,不过以一种线程安全的方式改变引用计数的类型会怎么样呢? +不幸的是,`Rc` 并不能安全的在线程间共享。当 `Rc` 管理引用计数时,它必须在每一个 `clone` 调用时增加计数,并在每一个克隆被丢弃时减少计数。`Rc` 并没有使用任何并发原语,来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致诡异的 bug,比如可能会造成内存泄漏,或在使用结束之前就丢弃一个值。那么如果有一个正好与 `Rc` 类似,而又以一种线程安全的方式改变引用计数的类型会怎么样呢? #### 原子引用计数 `Arc` -如果你思考过像之前那样的问题的话,你就是正确的。确实有一个类似`Rc`并可以安全的用于并发环境的类型:`Arc`。字母“a”代表**原子性**(*atomic*),所以这是一个**原子引用计数**(*atomically reference counted*)类型。原子性是另一类这里还未涉及到的并发原语;请查看标准库中`std::sync::atomic`的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。 +如果你想过之前的问题,答案是肯定的,确实有一个类似`Rc`并可以安全的用于并发环境的类型:`Arc`。字母“a”代表**原子性**(*atomic*),所以这是一个**原子引用计数**(*atomically reference counted*)类型。原子性是另一类这里还未涉及到的并发原语;请查看标准库中`std::sync::atomic`的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。 -那为什么不是所有的原始类型都是原子性的呢,然后为什么不是所有标准库中的类型都默认使用`Arc`实现呢?线程安全伴随一些性能惩罚,我们只希望在需要时才为此付出代价。如果只是在单线程中对值进行操作,因为并不需要原子性提供的保证,所以代码可以运行的更快。 +为什么不是所有的原始类型都是原子性的?为什么不是所有标准库中的类型都默认使用`Arc`实现?线程安全带来性能惩罚,我们希望只在必要时才为此买单。如果只是在单线程中对值进行操作,原子性提供的保证并无必要,代码可以因此运行的更快。 -回到之前的例子:`Arc`和`Rc`除了`Arc`内部的原子性之外他们是等价的。其 API 也是一样的,所以可以修改`use`行和`new`调用。列表 16-15 中的代码最终可以编译和运行: +回到之前的例子:`Arc`和`Rc`除了`Arc`内部的原子性之外没有区别。其 API 也相同,所以可以修改`use`行和`new`调用。列表 16-15 中的代码最终可以编译和运行: Filename: src/main.rs @@ -311,6 +311,6 @@ Result: 10 回忆一下`Rc`并没有避免所有可能的问题:我们也讨论了当两个`Rc`相互引用时的引用循环的可能性,这可能造成内存泄露。`Mutex`有一个类似的 Rust 同样也不能避免的问题:死锁。**死锁**(*deadlock*)是一个场景中操作需要锁定两个资源,而两个线程分别拥有一个锁并永远相互等待的问题。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中`Mutex`和`MutexGuard`的 API 文档会提供有用的信息。 -Rust 的类型系统和所有权(规则)确保了线程在更新共享值时拥有独占的访问权限,所以线程不会以一种不可预测的方式覆盖彼此的操作。虽然为了使一切正确运行而在编译器上花了一些时间,但是我们节省了未来可能需要重现只在线程以特定顺序执行时才会出现的诡异错误场景的时间。 +Rust 的类型系统和所有权规则,确保了线程在更新共享值时拥有独占的访问权限,所以线程不会以不可预测的方式覆盖彼此的操作。虽然为了使一切正确运行而在编译器上花了一些时间,但是我们节省了未来的时间,尤其是线程以特定顺序执行才会出现的诡异错误难以重现。 接下来,为了丰富本章的内容,让我们讨论一下`Send`和`Sync` trait 以及如何对自定义类型使用他们。 \ No newline at end of file diff --git a/src/ch17-01-what-is-oo.md b/src/ch17-01-what-is-oo.md index 886afb4..da56ec8 100644 --- a/src/ch17-01-what-is-oo.md +++ b/src/ch17-01-what-is-oo.md @@ -96,8 +96,8 @@ impl AveragedCollection { -为了支持这种模式,Rust 有 **trait 对象**(*trait objects*),这样我们也可以指明接受任意类型的值,只要这个值实现了一种特定的 trait。 +为了支持这种模式,Rust 有 **trait 对象**(*trait objects*),这样我们就可以接受任意类型的值,只要这个值实现了指定的 trait。 -继承最近在很多编程语言的设计方案中失宠了。使用继承来实现代码重用,会共享比实际需要更多的代码。子类不应该总是共享它们的父类的所有特性,但是继承意味着子类得到了它父类所有的数据和行为。这使得程序的设计更加不灵活,并产生了无意义的方法调用或子类,或者由于方法并不适用于子类,但必需从父类继承而造成错误的可能性。另外,一些语言只允许子类继承一个父类,这进一步限制了程序设计的灵活性。 +继承最近在很多编程语言的设计方案中失宠了。使用继承来实现代码重用,会共享更多非必需的代码。子类不应该总是共享其父类的所有特性,然而继承意味着子类得到了其父类全部的数据和行为。这使得程序的设计更不灵活,并产生了无意义的方法调用或子类,以及由于方法并不适用于子类,却必需从父类继承而可能造成的错误。另外,某些语言只允许子类继承一个父类,进一步限制了程序设计的灵活性。 因为这些原因,Rust 选择了一个另外的途径,使用 trait 对象替代继承。让我们看一下在 Rust 中 trait 对象是如何实现多态的。 diff --git a/src/ch17-02-trait-objects.md b/src/ch17-02-trait-objects.md index fd86a5a..4a0779d 100644 --- a/src/ch17-02-trait-objects.md +++ b/src/ch17-02-trait-objects.md @@ -4,17 +4,17 @@ >
> commit 67876e3ef5323ce9d394f3ea6b08cb3d173d9ba9 - 在第八章,我们谈到了 vector 的局限是 vector 只能存储同种类型的元素。在列表 8-1 中有一个例子,其中定义了一个有存放整型、浮点型和文本的成员的枚举类型`SpreadsheetCell`,这样就可以在每一个单元格储存不同类型的数据并使得 vector 仍让代表一行单元格。这在那类代码被编译时就知晓需要可交换处理的数据的类型是一个固定集合的情况下是可行的。 + 在第八章,我们谈到了 vector 只能存储同种类型元素的局限。在列表 8-1 中有一个例子,其中定义了存放包含整型、浮点型和文本型成员的枚举类型`SpreadsheetCell`,这样就可以在每一个单元格储存不同类型的数据,并使得 vector 仍然代表一行单元格。当编译时就知道类型集合全部元素的情况下,这种方案是可行的。 -有时,我们想我们使用的类型集合是可扩展的,可以被使用我们的库的程序员扩展。比如很多图形化接口工具有一个条目列表,从这个列表迭代和调用draw方法在每个条目上。我们将要创建一个库crate,包含称为`rust_gui`的CUI库的结构体。我们的GUI库可以包含一些给开发者使用的类型,比如`Button`或者`TextField`。使用`rust_gui`的程序员会创建更多可以在屏幕绘图的类型:一个程序员可能会增加`Image`,另外一个可能会增加`SelectBox`。我们不会在本章节实现一个完善的GUI库,但是我们会展示如何把各部分组合在一起。 +有时,我们需要可扩展的类型集合,能够被库的用户扩展。比如很多图形化接口工具有一个条目列表,迭代该列表并调用每个条目的 draw 方法。我们将创建一个库 crate,包含称为 `rust_gui` 的 GUI 库。库中有一些为用户准备的类型,比如 `Button` 或 `TextField`,`rust_gui`的用户还会创建更多,有的用户会增加`Image`,有的用户会增加`SelectBox`,然后用它们在屏幕上绘图。我们不会在本章节实现一个完善的GUI库,只是展示如何把各部分组合起来。 -当要写一个`rust_gui`库时,我们不知道其他程序员要创建什么类型,所以我们无法定义一个`enum`来包含所有的类型。我们知道的是`rust_gui`需要有能力跟踪所有这些不同类型的大量的值,需要有能力在每个值上调用`draw`方法。我们的GUI库不需要确切地知道当调用`draw`方法时会发生什么,只要值有可用的方法供我们调用就可以。 +当写 `rust_gui` 库时,我们不知道其他程序员需要什么类型,所以无法定义一个 `enum` 来包含所有的类型。然而 `rust_gui` 需要跟踪所有这些不同类型的值,需要有在每个值上调用 `draw` 方法能力。我们的 GUI 库不需要确切地知道调用 `draw` 方法会发生什么,只需要有可用的方法供我们调用。 -在有继承的语言里,我们可能会定义一个名为`Component`的类,该类上有一个`draw`方法。其他的类比如`Button`、`Image`和`SelectBox`会从`Component`继承并继承`draw`方法。它们会各自覆写`draw`方法来自定义行为,但是框架会把所有的类型当作是`Component`的实例,并在它们上调用`draw`。 +在可以继承的语言里,我们会定义一个名为 `Component` 的类,该类上有一个`draw`方法。其他的类比如`Button`、`Image`和`SelectBox`会从`Component`继承并拥有`draw`方法。它们各自覆写`draw`方法以自定义行为,但是框架会把所有的类型当作是`Component`的实例,并在其上调用`draw`。 ### 定义一个带有自定义行为的Trait @@ -104,7 +104,7 @@ impl Screen Listing 17-6: 一种`Screen`结构体的替代实现,它的`run`方法使用通用类型和trait绑定 - + 这个例子只能使我们的`Screen`实例的所有组件类型全是`Button`,或者全是`TextField`。如果你的组件集合是单一类型的,那么可以优先使用泛型和trait限定,这是因为其使用的具体类型在编译阶段可以被定意为是单一的。 而如果使用内部有`Vec>` trait对象的列表的`Screen`结构体,`Screen`实例可以同时包含`Box