如何返回对添加到特征对象向量的具体类型的引用?

时间:2019-04-21 16:32:19

标签: vector rust

在此代码中,我将获取一个向量,创建一个struct实例,并将其添加到带框的向量中:

trait T {}

struct X {}
impl T for X {}

fn add_inst(vec: &mut Vec<Box<T>>) -> &X {
    let x = X {};
    vec.push(Box::new(x));
    // Ugly, unsafe hack I made
    unsafe { std::mem::transmute(&**vec.last().unwrap()) }
}

很明显,它使用了mem::transmute,这让我觉得这不是正确的方法。难道这是唯一的方法吗?

此外,尽管它在Rust 1.32中编译,但在Rust 1.34中失败:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
  --> src/lib.rs:10:14
   |
10 |     unsafe { std::mem::transmute(&**vec.last().unwrap()) }
   |              ^^^^^^^^^^^^^^^^^^^
   |
   = note: source type: `&dyn T` (128 bits)
   = note: target type: `&X` (64 bits)

2 个答案:

答案 0 :(得分:3)

我认为这段代码是安全的:

fn add_inst(vec: &mut Vec<Box<dyn T>>) -> &X {
    let x = X {};
    let b = Box::new(x);
    let ptr = &*b as *const X;
    vec.push(b);
    unsafe { &*ptr }
}

诀窍是将原始指针保存到*const X,然后再将其转换为Box<dyn T>。然后,您可以将其转换回引用,然后再从函数中将其返回。 这是安全的,因为装箱的值永远不会移动(当然,除非将其从Box中移出),所以ptr在将b转换为Box<dyn T>的过程中仍然存在

答案 1 :(得分:3)

您的“丑陋骇客”实际上是完全不正确且不安全的。您很遗憾Rust 1.32没有报告该错误,但值得庆幸的是Rust 1.34不会报告该错误。

存储带框的值时,将创建一个薄指针。这会占用平台的本地整数大小(例如,在32位x86上为32位,在64位x86上为64位,等等):

+----------+
| pointer  |
| (0x1000) |
+----------+

存储盒装特征对象时,将创建一个 fat指针。它包含指向数据的相同指针和对 vtable 的引用。该指针的大小为两个本机整数:

+----------+----------+
| pointer  | vtable   |
| (0x1000) | (0xBEEF) |
+----------+----------+

通过尝试从trait对象到引用的转换,您正在丢失这些指针之一,但是未定义哪个指针。无法保证首先出现:数据指针或vtable。

一种解决方案将使用std::raw::TraitObject,但这是不稳定的,因为胖指针的布局仍然悬而未决。

我建议不使用unsafe代码的解决方案是使用Any

use std::any::Any;

trait T: Any {}

struct X {}
impl T for X {}

fn add_inst(vec: &mut Vec<Box<dyn T>>) -> &X {
    let x = X {};
    vec.push(Box::new(x));
    let l = vec.last().unwrap();
    Any::downcast_ref(l).unwrap()
}

如果您不能/不想使用AnyI've been told,则将特征对象指针转换为指向具体类型的指针将仅保留数据指针。不幸的是,我找不到为此的正式参考,这意味着尽管凭经验可以使用,但我无法完全保证该代码:

fn add_inst(vec: &mut Vec<Box<dyn T>>) -> &X {
    let x = X {};
    vec.push(Box::new(x));
    let last: &dyn T = &**vec.last().unwrap();

    // I copied this code from Stack Overflow without reading
    // it and it may not actually be safe.
    unsafe {
        let trait_obj_ptr = last as *const dyn T;
        let value_ptr = trait_obj_ptr as *const X;
        &*value_ptr
    }
}

另请参阅: