如何共享堆分配的特征对象?

时间:2019-03-16 16:15:24

标签: rust traits trait-objects

我有一个特征和一个实现该特征的结构(一个特征对象)。我想在堆上分配我的特征对象,并让其他结构引用它们。

Box字段

trait Material {}

struct Iron {}

impl Material for Iron {}

// It works, but takes ownership of boxes.
struct Sphere {
    radius: f64,
    material: Box<dyn Material>,
}

此代码有效,但是我不能有两个共享相同Material的球,因为Box拥有材料,而一个球拥有其Box字段。

参考字段

我的下一个尝试是使用普通引用而不是Box

struct Sphere<'a> {
    radius: f64,
    material: &'a dyn Material,
}

这也可行,但是据我了解,我的Material将分配在堆栈上,而不是堆上。如果Material的值确实很大,而我希望将其放在堆上怎么办?这将导致我进入无法编译的下一个方法:

参考盒子

struct Sphere<'a> {
    radius: f64,
    material: &'a Box<dyn Material>,
}

fn main() {
    let m1 = &Box::new(Iron {});
    let s1 = Sphere {
        radius: 1.0,
        material: m1,
    };
    assert_eq!(s1.radius, 1.0);
}

这给了我以下错误:

error[E0308]: mismatched types
  --> src/main.rs:16:19
   |
16 |         material: m1,
   |                   ^^ expected trait Material, found struct `Iron`
   |
   = note: expected type `&std::boxed::Box<(dyn Material + 'static)>`
              found type `&std::boxed::Box<Iron>`

我不太确定'static的类型来自何处,并且看起来混淆了类型检查器。否则,据我所知,dyn MaterialIron可以统一。

Playground

1 个答案:

答案 0 :(得分:4)

RcArc

当您需要共享所有权时,RcArc通常是第一个可以使用的工具。这些类型通过引用计数实现共享,因此克隆一个便宜(只需复制一个指针并增加refcount)。在这种情况下,任何一种都可以轻松工作:

struct Sphere {
    radius: f64,
    material: Rc<dyn Material>,
}

let m1 = Rc::new(Iron {});
let s1 = Sphere {
    radius: 1.0,
    material: m1,
};

m1的具体类型为Rc<Iron>,但是由于它实现了CoerceUnsized特性,因此在期望Rc<dyn Material>的上下文中可以是automatically coerced。您可以通过Sphere ing clone来使多个m1引用相同的材​​料。 (Full example

RcArc之间的区别在于,Arc可以安全地用于多个线程之间的共享,而Rc则不是。 (另请参见When to use Rc vs Box?

参考

作为参考示例:

  

这也可行,但是据我了解,我的材料将分配在堆栈上,而不是堆上。

生存时间确实是从堆栈派生的,但是引用本身不需要指向堆栈中的某些内容。例如,您可以通过取消引用T来引用Box<T>中的Box

struct Sphere<'a> {
    radius: f64,
    material: &'a dyn Material,
}

let m1 = Box::new(Iron {});
let s1 = Sphere {
    radius: 1.0,
    material: &*m1, // dereference the `Box` and get a reference to the inside
};
let s2 = Sphere {
    radius: 2.0,
    material: &*m1,
};

这比使用Rc更便宜,因为&引用是Copy可用的,但是即使Iron本身存储在堆中,指向的引用也指向它仍然绑定到 stack 变量m1的生存期。如果您不能使m1的寿命足够长,则可能要使用Rc而不是普通引用。

引用Box

这种方法也应该起作用,尽管这是不必要的。之所以没有这样做,是因为尽管您可以将Box<Iron>强制转换为Box<dyn Material>,但您不能强制将&Box<Iron>强制转换为{{1 }};类型不兼容。相反,您需要创建&Box<dyn Material>类型的堆栈变量,以便可以对其进行引用。

Box<dyn Material>