【问题标题】:How to share heap-allocated trait objects?如何共享堆分配的特征对象?
【发布时间】:2019-08-07 11:48:32
【问题描述】:

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

框字段

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,
}

这也有效,但据我了解,我的Materials 将分配在堆栈而不是堆上。如果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

【问题讨论】:

    标签: rust traits trait-objects


    【解决方案1】:

    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&lt;Iron&gt;,但因为它实现了CoerceUnsized 特征,所以在期望Rc&lt;dyn Material&gt; 的上下文中它可以是automatically coerced。您可以通过cloneing m1 使多个Spheres 引用相同的材​​料。 (Full example)

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

    参考文献

    至于您的参考示例:

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

    lifetimes 确实是从栈中派生出来的,但是引用本身并不需要指向栈上的东西。例如,您可以通过取消引用 Box 来引用 Box&lt;T&gt; 中的 T

    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 更便宜,因为&amp; 引用是Copyable,但是即使Iron 本身存储在堆上,指向它的引用仍然绑定到stack 变量m1。如果你不能让m1 活得足够长,你可能会想要使用Rc 而不是普通的引用。

    引用Box

    这种方法也应该有效,尽管它是不必要的。不这样做的原因是,尽管您可以将Box&lt;Iron&gt; 强制转换为Box&lt;dyn Material&gt;,但您不能&amp;Box&lt;Iron&gt; 强制转换为&amp;Box&lt;dyn Material&gt;;类型不兼容。相反,您需要创建一个 Box&lt;dyn Material&gt; 类型的堆栈变量,以便您可以引用它。

    let m1: Box<dyn Material> = Box::new(Iron {}); // coercion happens here
    let s1 = Sphere {
        radius: 1.0,
        material: &m1,  // so that this reference is the right type
    };
    

    【讨论】:

    • @dying_sphynx 我添加了一些解释。我认为还有另一个问题更详细地说明了为什么 &amp;T -> &amp;dyn Trait 是有效的强制,但 &amp;Box&lt;T&gt; -> &amp;Box&lt;dyn Trait&gt; 不是;如果我能找到它,我会链接到它。
    • 还有一件更有趣的事情:我尝试在您的完整示例中内联对 Rc::clone 的调用,但它没有用(失败,与您最后提到的引用强制相同的问题)。但是当我使用m1.clone() 而不是Rc::clone(&amp;m1) 时——它起作用了。这是出乎意料的,因为我认为Rc::clone(&amp;x)x.clone() 应该工作相同:playground
    • @dying_sphynx 我认为这是因为 method auto-deref and coercion 之间的差异(在这种情况下是未调整大小的强制,而不是 deref 强制)。 m1.clone() 将始终解析为m1 的类型,即Rc&lt;Iron&gt;。但是Rc::clone(&amp;m1) 需要推断Rc&lt;_&gt; 的参数,并且它不知道强制需要在函数调用“外部”发生,所以它选择了错误的东西(Rc&lt;dyn Material&gt; 而不是Rc&lt;Iron&gt;)。
    • 我已经通过引用尝试了您的建议:&amp;*m1 通过创建对 Box 内容的引用可以很好地工作,但看起来在这种情况下仅使用 &amp;m1 不起作用:@987654328 @
    猜你喜欢
    • 2019-01-08
    • 2019-07-25
    • 2020-06-23
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多