什么使某事成为特征对象"?

时间:2014-12-19 14:14:39

标签: rust traits

最近的Rust变化使得“特质对象”对我来说更加突出,但我只是模糊地掌握了什么让某些东西成为特质对象。特别是一个变化是upcoming change允许特征对象将特征实现转发到内部类型。

给定一个特征Foo,我很确定Box<Foo>是一个特征对象。 &Foo也是特质对象吗?那些其他智能指针的内容如RcArc呢?我怎样才能创建自己的类型作为特征对象呢?

reference只提到一次特征对象,但没有提到定义。

3 个答案:

答案 0 :(得分:44)

当您有指向特征的指针时,您有特征对象。 BoxArcRc和参考&都是其核心指针。在定义“特质对象”方面,它们以相同的方式工作。

“特质对象”是Rust对dynamic dispatch的看法。 这是一个我希望有助于显示特征对象的例子:

// define an example struct, make it printable
#[derive(Debug)]
struct Foo;

// an example trait
trait Bar {
    fn baz(&self);
}

// implement the trait for Foo
impl Bar for Foo {
    fn baz(&self) { println!("{:?}", self) }
}

// This is a generic function that takes any T that implements trait Bar.
// It must resolve to a specific concrete T at compile time.
// The compiler creates a different version of this function
// for each concrete type used to call it so &T here is NOT
// a trait object (as T will represent a known, sized type
// after compilation)
fn static_dispatch<T>(t: &T) where T:Bar {
    t.baz(); // we can do this because t implements Bar
}

// This function takes a pointer to a something that implements trait Bar
// (it'll know what it is only at runtime). &dyn Bar is a trait object.
// There's only one version of this function at runtime, so this
// reduces the size of the compiled program if the function
// is called with several different types vs using static_dispatch.
// However performance is slightly lower, as the &dyn Bar that 
// dynamic_dispatch receives is a pointer to the object +
// a vtable with all the Bar methods that the object implements.
// Calling baz() on t means having to look it up in this vtable.
fn dynamic_dispatch(t: &dyn Bar) {
    // ----------------^
    // this is the trait object! It would also work with Box<dyn Bar> or 
    // Rc<dyn Bar> or Arc<dyn Bar>
    //
    t.baz(); // we can do this because t implements Bar
}

fn main() {
    let foo = Foo;
    static_dispatch(&foo);
    dynamic_dispatch(&foo);
}

为了进一步参考,有一个很好的Trait Objects chapter of the Rust book

答案 1 :(得分:2)

简短答案:您只能将对象安全的特征变成特征对象。

对象安全特征:不能解析为具体实现类型的特征。实际上,有两个规则控制特征是否是对象安全的。

  1. 返回类型不是Self。
  2. 没有通用类型参数。

任何满足这两个规则的特征都可以用作特征对象。

对象安全的特征示例可以用作特征对象

trait Draw {
    fn draw(&self);
}

不能用作特征对象的特征示例

trait Draw {
    fn draw(&self) -> Self;
}

有关详细说明:https://doc.rust-lang.org/book/second-edition/ch17-02-trait-objects.html

答案 2 :(得分:1)

Trait 对象是动态调度的 Rust 实现。动态分派允许在运行时选择多态操作(特征方法)的一种特定实现。动态分派允许非常灵活的架构,因为我们可以在运行时交换函数实现。但是,与动态分派相关的运行时成本很小。

保存 trait 对象的变量/参数是胖指针,由以下组件组成:

  • 指向内存中对象的指针
  • 指向该对象的虚表的指针,虚表是一个带有指向实际方法实现的指针的表。

示例

struct Point {
    x: i64,
    y: i64,
    z: i64,
}

trait Print {
    fn print(&self);
}

// dyn Print is actually a type and we can implement methods on it
impl dyn Print + 'static {
    fn print_traitobject(&self) {
        println!("from trait object");
    }
}

impl Print for Point {
    fn print(&self) {
        println!("x: {}, y: {}, z: {}", self.x, self.y, self.z);
    }
}

// static dispatch (compile time): compiler must know specific versions
// at compile time generates a version for each type

// compiler will use monomorphization to create different versions of the function
// for each type. However, because they can be inlined, it generally has a faster runtime
// compared to dynamic dispatch
fn static_dispatch<T: Print>(point: &T) {
    point.print();
}

// dynamic dispatch (run time): compiler doesn't need to know specific versions
// at compile time because it will use a pointer to the data and the vtable.
// The vtable contains pointers to all the different different function implementations.
// Because it has to do lookups at runtime it is generally slower compared to static dispatch

// point_trait_obj is a trait object
fn dynamic_dispatch(point_trait_obj: &(dyn Print + 'static)) {
    point_trait_obj.print();
    point_trait_obj.print_traitobject();
}

fn main() {
    let point = Point { x: 1, y: 2, z: 3 };

    // On the next line the compiler knows that the generic type T is Point
    static_dispatch(&point);

    // This function takes any obj which implements Print trait
    // We could, at runtime, change the specfic type as long as it implements the Print trait
    dynamic_dispatch(&point);
}