|
|
@ -217,20 +217,38 @@ fn main() {
|
|
|
|
```
|
|
|
|
```
|
|
|
|
因为`String`类型没有实现`Draw`特征,编译器直接就会报错,不会让上述代码运行。如果想要`String`类型被渲染在屏幕上,那么只需要为其实现`Draw`特征即可,非常容易。
|
|
|
|
因为`String`类型没有实现`Draw`特征,编译器直接就会报错,不会让上述代码运行。如果想要`String`类型被渲染在屏幕上,那么只需要为其实现`Draw`特征即可,非常容易。
|
|
|
|
|
|
|
|
|
|
|
|
#### &和dyn的区别
|
|
|
|
#### &dyn和Box\<dyn\>的区别
|
|
|
|
前文提到,`&`和`dyn`都可以用于特征对象,因此在功能上`&`和`dyn`几无区别,唯一的区别就是:`&`减少了一次指针调用。
|
|
|
|
前文提到,`&dyn`和`Box<dyn>`都可以用于特征对象,因此在功能上`&dyn`和`Box<dyn>`几无区别,唯一的区别就是:`&dyn`减少了一次指针调用。
|
|
|
|
|
|
|
|
|
|
|
|
因为`dyn`是一个宽指针(`fat pointer`), 它内部保存一个指针指向`vtable`,然后通过`vtable`查询到具体的函数指针,最后进行调用.
|
|
|
|
因为`Box<dyn>`是一个宽指针(`fat pointer`), 它内部保存一个指针指向`vtable`,然后通过`vtable`查询到具体的函数指针,最后进行调用.
|
|
|
|
|
|
|
|
|
|
|
|
所以,如果你在乎性能,又想使用特征对象简化代码,可以优先考虑`&`。
|
|
|
|
所以,如果你在乎性能,又想使用特征对象简化代码,可以优先考虑`&dyn`。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
注意`dyn`不能单独作为特征对象的定义,例如下面的代码编译器会报错,原因是特征对象可以是任意实现了某个特征的类型,编译器在编译期不知道该类型的大小。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
而`&dyn`和`Box<dyn>`在编译期都是已知大小,所以可以用作特征对象的定义。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
|
|
|
fn draw2(x: dyn Draw) {
|
|
|
|
|
|
|
|
x.draw();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
10 | fn draw2(x: dyn Draw) {
|
|
|
|
|
|
|
|
| ^ doesn't have a size known at compile-time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
= help: the trait `Sized` is not implemented for `(dyn Draw + 'static)`
|
|
|
|
|
|
|
|
help: function arguments must have a statically known size, borrowed types always have a known size
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 特征对象的动态分发
|
|
|
|
## 特征对象的动态分发
|
|
|
|
|
|
|
|
|
|
|
|
回一下泛型章节我们提到过的,泛型是在编译期完成处理的:编译器会为每一个泛型参数对应的具体类型生成一份代码,这种方式是**静态分发(static dispatch)**,因为是在编译期完成的,对于运行期性能完全没有任何影响。
|
|
|
|
回忆一下泛型章节我们提到过的,泛型是在编译期完成处理的:编译器会为每一个泛型参数对应的具体类型生成一份代码,这种方式是**静态分发(static dispatch)**,因为是在编译期完成的,对于运行期性能完全没有任何影响。
|
|
|
|
|
|
|
|
|
|
|
|
与静态分发相对应的是**动态分发(dynamic dispatch)**,在这种情况下,直到运行时,才能确定需要调用什么方法。
|
|
|
|
与静态分发相对应的是**动态分发(dynamic dispatch)**,在这种情况下,直到运行时,才能确定需要调用什么方法。
|
|
|
|
|
|
|
|
|
|
|
|
当使用特赠对象时,Rust 必须使用动态分发。编译器无法知晓所有可能用于特征对象代码的类型,所以它也不知道应该调用哪个类型的哪个方法实现。为此,Rust 在运行时使用特征对象中的指针来知晓需要调用哪个方法。动态分发也阻止编译器有选择的内联方法代码,这会相应的禁用一些优化。
|
|
|
|
当使用特征对象时,Rust 必须使用动态分发。编译器无法知晓所有可能用于特征对象代码的类型,所以它也不知道应该调用哪个类型的哪个方法实现。为此,Rust 在运行时使用特征对象中的指针来知晓需要调用哪个方法。动态分发也阻止编译器有选择的内联方法代码,这会相应的禁用一些优化。
|
|
|
|
|
|
|
|
|
|
|
|
## Self与self
|
|
|
|
## Self与self
|
|
|
|
在Rust中,有两个`self`,一个指代当前的实例对象,一个指代特征或者方法类型的别名:
|
|
|
|
在Rust中,有两个`self`,一个指代当前的实例对象,一个指代特征或者方法类型的别名:
|
|
|
@ -264,7 +282,7 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
|
|
对象安全对于特征对象是必须的,因为一旦有了特征对象,就不再知道实现该特征的具体类型是什么了。如果特征方法返回具体的`Self`类型,但是特征对象忘记了其真正的类型,那这个`Self`就非常尴尬,因为没人知道它是谁了。同理对于泛型类型参数来说,当使用特征时其会放入具体的类型参数:此具体类型变成了实现该特征的类型的一部分。当使用特征对象时其具体类型被抹去了,故而无从得知放入泛型参数类型到底是什么。
|
|
|
|
对象安全对于特征对象是必须的,因为一旦有了特征对象,就不再知道实现该特征的具体类型是什么了。如果特征方法返回具体的`Self`类型,但是特征对象忘记了其真正的类型,那这个`Self`就非常尴尬,因为没人知道它是谁了。同理对于泛型类型参数来说,当使用特征时其会放入具体的类型参数:此具体类型变成了实现该特征的类型的一部分。当使用特征对象时其具体类型被抹去了,故而无从得知放入泛型参数类型到底是什么。
|
|
|
|
|
|
|
|
|
|
|
|
标准库中的 `Clone`特征就符合对象安全的要求:
|
|
|
|
标准库中的 `Clone`特征就不符合对象安全的要求:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
pub trait Clone {
|
|
|
|
pub trait Clone {
|
|
|
|