ch17-02翻译90%

pull/13/head
itfanr 8 years ago
parent a35292be38
commit edaeab0c0b

@ -18,7 +18,7 @@ get Chapter 8 for editing. /Carol -->
在有继承的语言里,我们可能会定义一个名为`Component`的类,该类上有一个`draw`方法。其他的类比如`Button`、`Image`和`SelectBox`会从`Component`继承并继承`draw`方法。它们会各自覆写`draw`方法来自定义行为,但是框架会把所有的类型当作是`Component`的实例,并在它们上调用`draw`。
## 定义一个带有自定义行为的Trait
### 定义一个带有自定义行为的Trait
不过在Rust语言中我们可以定义一个名为`Draw`的trait其上有一个名为`draw`的方法。我们定义一个带有*trait对象*的vector绑定了一种指针的trait比如`&`引用或者一个`Box<T>`智能指针。
@ -111,7 +111,7 @@ impl<T> Screen<T>
而如果使用内部有`Vec<Box<Draw>>` trait对象的列表的`Screen`结构体,`Screen`实例可以同时包含`Box<Button>`和`Box<TextField>`的`Vec`。我们看它是怎么工作的,然后讨论运行时性能的实现。
### 来自我们或者库使用者的实现
### 来自我们或者库使用者的实现
现在,我们增加一些实现了`Draw`trait的类型。我们会再次提供`Button`实际上实现一个GUI库超出了本书的范围所以`draw`方法的内部不会有任何有用的实现。为了想象一下实现可能的样子,`Button`结构体可能有 width`、`height`和`label`字段如Listing 17-7所示
@ -135,8 +135,7 @@ impl Draw for Button {
}
```
<span class="caption">Listing 17-7: A `Button` struct that implements the
`Draw` trait</span>
<span class="caption">Listing 17-7: 实现了`Draw` trait的`Button` 结构体</span>
在`Button`上的 `width`、`height`和`label`会和其他组件不同,比如`TextField`可能有`width`、`height`,
`label``placeholder`字段。每个我们可以在屏幕上绘制的类型会实现`Draw`trait在`draw`方法中使用不同的代码,定义了如何绘制`Button`GUI代码的具体实现超出了本章节的范围。除了`Draw` trait`Button`可能也有另一个`impl`块,包含了当按钮被点击的时候的响应方法。这类方法不适用于`TextField`这样的类型。
@ -165,11 +164,7 @@ impl Draw for SelectBox {
<span class="caption">Listing 17-8: 另外一个crate中在`SelectBox`结构体上使用`rust_gui`和实现了`Draw` trait
</span>
The user of our library can now write their `main` function to create a
`Screen` instance and add a `SelectBox` and a `Button` to the screen by putting
each in a `Box<T>` to become a trait object. They can then call the `run`
method on the `Screen` instance, which will call `draw` on each of the
components. Listing 17-9 shows this implementation:
我们的库的使用者现在可以写他们的`main`函数来创建一个`Screen`实例,然后通过把自身放入`Box<T>`变成trait对象向screen增加`SelectBox` 和`Button`。它们可以在每个`Screen`实例上调用`run`方法,这会调用每个组件的`draw`方法。 Listing 17-9展示了实现
<span class="filename">Filename: src/main.rs</span>
@ -200,32 +195,16 @@ fn main() {
}
```
<span class="caption">Listing 17-9: Using trait objects to store values of
different types that implement the same trait</span>
Even though we didn't know that someone would add the `SelectBox` type someday,
our `Screen` implementation was able to operate on the `SelectBox` and draw it
because `SelectBox` implements the `Draw` type, which means it implements the
`draw` method.
Only being concerned with the messages a value responds to, rather than the
value's concrete type, is similar to a concept called *duck typing* in
dynamically typed languages: if it walks like a duck, and quacks like a duck,
then it must be a duck! In the implementation of `run` on `Screen` in Listing
17-5, `run` doesn't need to know what the concrete type of each component is.
It doesn't check to see if a component is an instance of a `Button` or a
`SelectBox`, it just calls the `draw` method on the component. By specifying
`Box<Draw>` as the type of the values in the `components` vector, we've defined
that `Screen` needs values that we can call the `draw` method on.
The advantage with using trait objects and Rust's type system to do duck typing
is that we never have to check that a value implements a particular method at
runtime or worry about getting errors if a value doesn't implement a method but
we call it. Rust won't compile our code if the values don't implement the
traits that the trait objects need.
For example, Listing 17-10 shows what happens if we try to create a `Screen`
with a `String` as a component:
<span class="caption">Listing 17-9: 使用trait对象来存储实现了相同trait的不同类型
</span>
虽然我们不知道有些人可能有一天会增加`SelectBox`类型,但是我们的`Screen` 有能力操作`SelectBox`和绘制,因为`SelectBox`实现了`Draw`类型,这意味着它实现了`draw`方法。
只关心值响应的消息,而不关心值的具体类型,这类似于动态类型语言中的*duck typing*如果它像鸭子一样走路像鸭子一样叫那么它肯定是只鸭子在Listing 17-5的`Screen`的`run`方法的实现中,`run`不需要知道每个组件的具体类型。它也不检查是否一个组件是`Button`或者`SelectBox`的实例,只是调用组件的`draw`方法即可。通过指定`Box<Draw>`作为`components`vector中的值类型我们定义了`Screen`需要可以被调用其`draw`方法的值。
使用trait对象和支持duck typing的Rust类型系统的好处是我们永远不需要在运行时检查一个值是否实现了一个特殊方法或者担心因为调用了一个值没有实现方法而遇到错误。如果值没有实现trait对象需要的traitRust不会编译我们的代码。
比如Listing 17-10展示了当我们创建一个把`String`当做其成员的`Screen`时发生的情况:
<span class="filename">Filename: src/main.rs</span>
@ -244,10 +223,11 @@ fn main() {
}
```
<span class="caption">Listing 17-10: Attempting to use a type that doesn't
implement the trait object's trait</span>
<span class="caption">Listing 17-10: 尝试使用一种没有实现trait对象的trait的类型
We'll get this error because `String` doesn't implement the `Draw` trait:
</span>
我们会遇到这个错误,因为`String`没有实现 `Draw`trait
```text
error[E0277]: the trait bound `std::string::String: Draw` is not satisfied
@ -260,31 +240,15 @@ error[E0277]: the trait bound `std::string::String: Draw` is not satisfied
= note: required for the cast to the object type `Draw`
```
This lets us know that either we're passing something we didn't mean to pass to
`Screen` and we should pass a different type, or we should implement `Draw` on
`String` so that `Screen` is able to call `draw` on it.
### Trait Objects Perform Dynamic Dispatch
Recall in Chapter 10 when we discussed the process of monomorphization that the
compiler performs when we use trait bounds on generics: the compiler generates
non-generic implementations of functions and methods for each concrete type
that we use in place of a generic type parameter. The code that results from
monomorphization is doing *static dispatch*: when the method is called, the
code that goes with that method call has been determined at compile time, and
looking up that code is very fast.
When we use trait objects, the compiler can't perform monomorphization because
we don't know all the types that might be used with the code. Instead, Rust
keeps track of the code that might be used when a method is called and figures
out at runtime which code needs to be used for a particular method call. This
is known as *dynamic dispatch*, and there's a runtime cost when this lookup
happens. Dynamic dispatch also prevents the compiler from choosing to inline a
method's code, which prevents some optimizations. We did get extra flexibility
in the code that we wrote and were able to support, though, so it's a tradeoff
to consider.
### Object Safety is Required for Trait Objects
这个报错让我们知道,或者我们传入了本来不想传给`Screen`的东西,我们应该传入一个不同的类型,或者是我们应该在`String`上实现`Draw`,这样,`Screen`才能调用它的`draw`方法。
### Trait对象执行动态分发
回忆一下第10章我们讨论过当我们使用通用类型的trait绑定时编译器执行单类型的处理过程在我们需要使用通用类型参数的地方编译器为每个实体类型产生了非通用的函数实现和方法。由于非单类型而产生的代码是 *static dispatch*:当方法被调用,代码会执行在编译阶段就决定的方法,这样寻找那段代码是非常快速的。
当我们使用trait对象编译器不能执行单类型的因为我们不知道可能被代码调用的类型。而当方法被调用的时候Rust跟踪可能被使用的代码然后在运行时找出为了方法被调用时该使用哪些代码。这也是我们熟知的*dynamic dispatch*,当运行时的查找发生时是比较耗费资源的。动态分发也防止编译器选择内联函数的代码,这样防止了一些优化。虽然我们写代码时得到了额外的代码灵活性,不过,这是一个权衡考虑。
### Trait 对象需要对象安全
<!-- Liz: we're conflicted on including this section. Not being able to use a
trait as a trait object because of object safety is something that

Loading…
Cancel
Save