diff --git a/src/ch01-02-hello-world.md b/src/ch01-02-hello-world.md index 6d24807..65b7a6f 100644 --- a/src/ch01-02-hello-world.md +++ b/src/ch01-02-hello-world.md @@ -123,7 +123,7 @@ $ ./main # or .\main.exe on Windows Cargo 是 Rust 的构建系统和包管理工具,同时 Rustacean 们使用 Cargo 来管理他们的 Rust 项目,因为它使得很多任务变得更轻松。例如,Cargo 负责构建代码、下载代码依赖的库并编译这些库。我们把代码需要的库叫做 **依赖**(*dependencies*)。 -最简单的 Rust 程序,例如我们刚刚编写的,并没有任何依赖,所以目前我们只使用了 Cargo 负责构建代码的那一部分。随着编写更加复杂的 Rust 程序,你会想要添加依赖,那么如果你使用 Cargo 开始的话,这将会变得简单许多。 +最简单的 Rust 程序,例如我们刚刚编写的,并没有任何依赖,所以目前我们只使用了 Cargo 负责构建代码的那一部分。随着编写更加复杂的 Rust 程序,你会想要添加依赖,如果你使用 Cargo 开始的话,这将会变得简单许多。 由于绝大部分 Rust 项目使用 Cargo,本书接下来的部分将假设你使用它。如果使用安装章节介绍的官方安装包的话,Rust 自带 Cargo。如果通过其他方式安装 Rust 的话,可以在终端输入如下命令检查是否安装了 Cargo: diff --git a/src/ch04-01-what-is-ownership.md b/src/ch04-01-what-is-ownership.md index b08545a..a2b1f21 100644 --- a/src/ch04-01-what-is-ownership.md +++ b/src/ch04-01-what-is-ownership.md @@ -25,26 +25,26 @@ Rust 的核心功能(之一)是**所有权**(*ownership*)。虽然这个 > 相反对于在编译时未知大小或大小可能变化的数据,可以把他们储存在堆上。堆是缺乏组织的:当向堆放入数据时,我们请求一定大小的空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回给我们一个它位置的指针。这个过程称作**在堆上分配内存**(*allocating on the heap*),并且有时这个过程就简称为“分配”(allocating)。向栈中放入数据并不被认为是分配。因为指针是已知的固定大小的,我们可以将指针储存在栈上,不过当需要实际数据时,必须访问指针。 > > 想象一下去餐馆就坐吃饭。当进入时,你说明有几个人,餐馆员工会找到一个够大的空桌子并领你们过去。如果有人来迟了,他们也可以通过询问来找到你们坐在哪。 -> +> > 访问堆上的数据要比访问栈上的数据要慢因为必须通过指针来访问。现代的处理器在内存中跳转越少就越快。继续类比,假设有一台服务器来处理来自多个桌子的订单。它在处理完一个桌子的所有订单后再移动到下一个桌子是最有效率的。从桌子 A 获取一个订单,接着再从桌子 B 获取一个订单,然后再从桌子 A,然后再从桌子 B 这样的流程会更加缓慢。出于同样原因,处理器在处理的数据之间彼此较近的时候(比如在栈上)比较远的时候(比如可能在堆上)能更好的工作。在堆上分配大量的空间也可能消耗时间。 > > 当调用一个函数,传递给函数的值(包括可能指向堆上数据的指针)和函数的局部变量被压入栈中。当函数结束时,这些值被移出栈。 -> +> > 记录何处的代码在使用堆上的什么数据,最小化堆上的冗余数据的数量以及清理堆上不再使用的数据以致不至于耗尽空间,这些所有的问题正是所有权系统要处理的。一旦理解了所有权,你就不需要经常考虑栈和堆了,不过理解如何管理堆内存可以帮助我们理解所有权为何存在以及为什么以这种方式工作。 ### 所有权规则 -首先,让我们看一下所有权的规则。请记住这些规则因为我们将讲解一些说明这些规则的例子: +首先,让我们看一下所有权的规则。请记住它们,我们将讲解一些它们的例子: -> 1. Rust 中的每一个值都有一个叫做它的**所有者**(*owner*)的变量。 -> 2. 同时一次只能有一个所有者 -> 3. 当所有者变量离开作用域,这个值将被丢弃。 +> 1. 每一个值都被它的**所有者**(*owner*)变量拥有。 +> 2. 值在任意时刻只能被一个所有者拥有。 +> 3. 当所有者离开作用域,这个值将被丢弃。 ### 变量作用域 -我们在第二章已经完成过一个 Rust 程序的例子了。现在我们已经掌握了基本语法,所以不会在所有的例子中包含`fn main() {`代码了,所以如果你是一路跟过来的,必须手动将之后例子的代码放入一个`main`函数中。为此,例子将显得更加具体,使我们可以关注具体细节而不是样板代码。 +我们已经在第二章完成过一个 Rust 程序的例子。现在我们已经掌握了基本语法,所以不会在所有的例子中包含`fn main() {`代码了,所以如果你是一路跟过来的,必须手动将之后例子的代码放入一个`main`函数中。为此,例子将显得更加具体,使我们可以关注具体细节而不是样板代码。 作为所有权的第一个例子,我们看看一些变量的**作用域**(*scope*)。作用域是一个项(原文:item)在程序中有效的范围。假如有一个这样的变量: diff --git a/src/ch04-02-references-and-borrowing.md b/src/ch04-02-references-and-borrowing.md index a8bf6ad..54634ca 100644 --- a/src/ch04-02-references-and-borrowing.md +++ b/src/ch04-02-references-and-borrowing.md @@ -58,7 +58,7 @@ fn calculate_length(s: &String) -> usize { // s is a reference to a String 我们将获取引用作为函数参数称为**借用**(*borrowing*)。正如现实生活中,如果一个人拥有某样东西,你可以从它哪里借来。当你使用完毕,必须还回去。 -那么如果我们尝试修改借用的变量呢?尝试列表 4-9 中的代码。剧透:这行不通! +如果我们尝试修改借用的变量呢?尝试列表 4-9 中的代码。剧透:这行不通! Filename: src/main.rs @@ -137,11 +137,11 @@ error[E0499]: cannot borrow `s` as mutable more than once at a time **数据竞争**是一种特定类型的竞争状态,它可由这三个行为造成: -1. 两个或更多指针同时访问相同的数据。 -2. 至少有一个指针被用来写数据。 -3. 没有被用来同步数据访问的机制。 +1. 两个或更多指针同时访问同一数据。 +2. 至少有一个指针被写入。 +3. 没有同步数据访问的机制。 -数据竞争会导致未定义行为并且当在运行时尝试追踪时可能会变得难以诊断和修复;Rust 阻止了这种情况的发生,因为存在数据竞争的代码根本就不能编译! +数据竞争会导致未定义行为,在运行时难以追踪,并且难以诊断和修复;Rust 避免了这种情况,它拒绝编译存在数据竞争的代码! 一如既往,可以使用大括号来创建一个新的作用域来允许拥有多个可变引用,只是不能**同时**拥有: diff --git a/src/ch08-02-strings.md b/src/ch08-02-strings.md index 15d8e58..6a5e17e 100644 --- a/src/ch08-02-strings.md +++ b/src/ch08-02-strings.md @@ -223,7 +223,7 @@ let s = &hello[0..4]; 这里,`s`是一个`&str`,它包含字符串的头四个字节。早些时候,我们提到了这些字母都是两个字节长的,所以这意味着`s`将会是“Зд”。 -那么如果获取`&hello[0..1]`会发生什么呢?回答是:在运行时会 panic,就跟访问 vector 中的无效索引时一样: +如果获取`&hello[0..1]`会发生什么呢?答案是:在运行时会 panic,就跟访问 vector 中的无效索引时一样: ```text 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..5ef8f80 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/ch16-04-extensible-concurrency-sync-and-send.md b/src/ch16-04-extensible-concurrency-sync-and-send.md index 046ba30..60c07cf 100644 --- a/src/ch16-04-extensible-concurrency-sync-and-send.md +++ b/src/ch16-04-extensible-concurrency-sync-and-send.md @@ -4,7 +4,7 @@ >
> commit 55b294f20fc846a13a9be623bf322d8b364cee77 -Rust 的并发模型中一个有趣的方面是:语言本身对并发知道的**很少**。几乎我们之前讨论的所有内容,都是标准库的一部分,而不是语言本身的内容。因为并不需要语言提供任何用于并发上下文中的内容,并发选择也不仅限于标准库或语言所提供的:我们可以编写自己的或使用别人编写的内容。 +Rust 的并发模型中一个有趣的方面是:语言本身对并发知之**甚少**。我们之前讨论的几乎所有内容,都属于标准库,而不是语言本身的内容。由于不需要语言提供并发相关的基础设施,并发方案不受标准库或语言所限:我们可以编写自己的或使用别人编写的。 我们说“**几乎**所有内容都不属于语言本身”,那么属于语言本身的是什么呢?是两个 trait,都位于`std::marker`: `Sync`和`Send`。 diff --git a/src/ch17-01-what-is-oo.md b/src/ch17-01-what-is-oo.md index 886afb4..263beb9 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..1e1af87 100644 --- a/src/ch17-02-trait-objects.md +++ b/src/ch17-02-trait-objects.md @@ -4,25 +4,25 @@ >
> 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 -不过,在Rust语言中,我们可以定义一个名为`Draw`的trait,其上有一个名为`draw`的方法。我们定义一个带有*trait对象*的vector,绑定了一种指针的trait,比如`&`引用或者一个`Box`智能指针。 +不过,在Rust语言中,我们可以定义一个 `Draw` trait,包含名为 `draw` 的方法。我们定义一个由*trait对象*组成的vector,绑定了某种指针的trait,比如`&`引用或者一个`Box`智能指针。 -我们提到,我们不会称结构体和枚举为对象,这是为了区分于其他语言的结构体和枚举对象。结构体或者枚举成员中的数据和`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。 Filename: src/lib.rs @@ -36,7 +36,7 @@ pub trait Draw { -因为我们已经在第10章讨论过如何定义trait,你可能比较熟悉。下面是新的定义:Listing 17-4有一个名为`Screen`的结构体,里面有一个名为`components`的vector,`components`的类型是Box。`Box`是一个trait对象:它是`Box`内部任意一个实现了`Draw`trait的类型的替身。 +因为我们已经在第10章讨论过如何定义 trait,你可能比较熟悉。下面是新的定义:Listing 17-4有一个名为 `Screen` 的结构体,里面有一个名为 `components` 的 vector,`components` 的类型是Box。`Box` 是一个 trait 对象:它是 `Box` 内部任意一个实现了 `Draw` trait 的类型的替身。 Filename: src/lib.rs @@ -50,11 +50,11 @@ pub struct Screen { } ``` -Listing 17-4: 定义一个`Screen`结构体,带有一个含有实现了`Draw`trait的`components` vector成员 +Listing 17-4: 定义一个 `Screen` 结构体,带有一个含有实现了 `Draw` trait 的 `components` vector 成员 -在`Screen`结构体上,我们将要定义一个`run`方法,该方法会在它的`components`上调用`draw`方法,如Listing 17-5所示: +在 `Screen` 结构体上,我们将要定义一个 `run` 方法,该方法会在它的 `components` 上调用 `draw` 方法,如Listing 17-5所示: Filename: src/lib.rs @@ -76,10 +76,10 @@ impl Screen { } ``` -Listing 17-5:在`Screen`上实现一个`run`方法,该方法在每个组件上调用`draw`方法 +Listing 17-5:在 `Screen` 上实现一个 `run` 方法,该方法在每个组件上调用 `draw` 方法 -这不同于定义一个使用带有trait限定的泛型参数的结构体。泛型参数一次只能被一个实体类型替代,而trait对象可以在运行时允许多种实体类型填充trait对象。比如,我们已经定义了`Screen`结构体使用泛型和一个trait限定,如Listing 17-6所示: +这与带 trait 约束的泛型结构体不同(trait 约束泛型参数)。泛型参数一次只能被一个具体类型替代,而 trait 对象可以在运行时允许多种具体类型填充 trait 对象。比如,我们已经定义了 `Screen` 结构体使用泛型和一个 trait 约束,如Listing 17-6所示: Filename: src/lib.rs @@ -102,16 +102,16 @@ impl Screen } ``` -Listing 17-6: 一种`Screen`结构体的替代实现,它的`run`方法使用通用类型和trait绑定 +Listing 17-6: 一种 `Screen` 结构体的替代实现,它的 `run` 方法使用通用类型和 trait 绑定 - -这个例子只能使我们的`Screen`实例的所有组件类型全是`Button`,或者全是`TextField`。如果你的组件集合是单一类型的,那么可以优先使用泛型和trait限定,这是因为其使用的具体类型在编译阶段可以被定意为是单一的。 -而如果使用内部有`Vec>` trait对象的列表的`Screen`结构体,`Screen`实例可以同时包含`Box