当前位置:嗨网首页>书籍在线阅读

18-特征对象

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

4.6.2 特征对象

到目前为止,我们经常看到的都是在静态分发上下文中特征的应用,即在泛型API中设置特征区间。不过,我们还有另一种创建多态API的方法,可以将参数指定为实现某个特征的东西,而不是泛型或具体类型。这种方法被声明为实现某个特征API,即特征对象。特征对象类似C++中的虚方法。特征对象实现为胖指针,并且是不定长类型,这意味着它们只能在引用符号(&)后面使用。我们将在第7章详细介绍不定长类型。特征对象胖指针具有指向与对象关联的实际数据的第一指针,而第二指针指向虚拟表(vtable),它是在固定偏移处为每个对象的方法保留一个函数指针的结构体。

特征对象是Rust执行动态分发的方式,我们没有实际的具体类型信息。通过跳转到vtable并调用适当的方法完成方法解析。特征对象的另一个用例是,它们允许用户对可以具有多种类型的集合进行操作,但是在运行时需要额外的间接指针引用开销。为了说明这一点,请考虑如下程序:

// trait_objects.rs
use std::fmt::Debug;
#[derive(Debug)]
struct Square(f32);
#[derive(Debug)]
struct Rectangle(f32, f32);
trait Area: Debug {
    fn get_area(&self) -> f32;
}
impl Area for Square {
    fn get_area(&self) -> f32 {
        self.0 * self.0
    }
}
impl Area for Rectangle {
    fn get_area(&self) -> f32 {
        self.0 * self.1
    }
}
fn main() {
    let shapes: Vec<&dyn Area> = vec![&Square(3f32), &Rectangle(4f32,
2f32)];
    for s in shapes {
        println!("{:?}", s);
    }
}

如你所见,shapes的元素类型是&dyn Area,这是一种表示为特征的类型。特征对象是由dyn Area表示的,意味着它是指向Area特征某些实现的指针。特征对象形式的类型允许用户在集合类型(例如Vec)中存储不同类型。在前面的示例中,Square和Rectangle会隐式转换成特征对象,因为我们给它们推送了一个引用。我们还可以通过手动转换某个特征对象来构造一个类型,但这是一种比较少见的情况,只有在编译器自身无法将类型作为特征对象转换时使用。请注意,我们只能创建在编译时知道类型尺寸的特征对象。dyn Trait是一个不定长类型,只能作为引用创建。我们还可以通过将特征对象置于其他指针类型之后来创建特征对象,例如Box、Rc、Arc等。

79.png 注意

在较早的Rust 2015中,特征对象仅表示为特征的名称,对于特征对象dyn Foo,它表示为Foo。该语法会令人困惑,在最新的Rust 2018中已被弃用。

在下面的代码中,我们将演示把dyn Trait作为函数中的参数使用的情形:

// dyn_trait.rs
use std::fmt::Display;
fn show_me(item: &dyn Display) {
    println!("{}", item);
}
fn main() {
    show_me(&"Hello trait object");
}

特征和泛型通过单态化(早期绑定)或运行时多态(后期绑定)提供了两种代码复用的方式。何时使用它们取决于具体情况和相关应用程序的需求。通常,错误类型会被分配到动态分发的序列,因为它们应该是很少被执行的代码路径。单态化对小型的应用场景来说非常方便,但是缺点是导致了代码的膨胀和重复,这会影响缓存效率,并增加二进制文件的大小。但是,在这两个选项中,静态分发应该是首选,除非系统对二进制文件大小存在严格的限制。