繁体   English   中英

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

[英]How do I return a reference to a concrete type that was added to a vector of trait objects?

在此代码中,我将获取一个向量,创建一个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)

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

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>转换为Box<dyn T>

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

存储装箱的值时,将创建一个瘦指针 这占用了整数的平台本机大小(例如,在32位x86上为32位,在64位x86上为64位,等等):

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

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

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

通过尝试执行从trait对象到引用的转换,您将丢失这些指针之一,但是并没有定义哪个指针。 不能保证先出现:数据指针或vtable。

一种解决方案将使用std::raw::TraitObject ,但这是不稳定的,因为胖指针的布局仍然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()
}

如果您不愿意/不希望使用Any ,那么我被告知将特征对象指针转换为具体类型的指针只会保留数据指针。 不幸的是,我找不到对此的正式参考,这意味着我无法完全保证该代码,尽管它在经验上是可行的:

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

也可以看看:

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM