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

问题描述 投票:1回答:2

在这段代码中,我使用一个向量,创建一个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)
vector rust
2个回答
3
投票

你的“丑陋的黑客”实际上是完全不正确和不安全的。你不幸的是,Rust 1.32没有报告错误,但幸运的是Rust 1.34确实如此。

存储盒装值时,可以创建精简指针。这占用了整数的平台本机大小(例如32位x86上的32位,64位x86上的64位等):

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

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

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

通过尝试从特征对象到引用执行转换,您正在丢失其中一个指针,但它没有定义哪个指针。首先无法保证:数据指针或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()
}

如果您不能/不想使用Any,那么将特征对象指针转换为指向具体类型的指针的I'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
    }
}

也可以看看:


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>

© www.soinside.com 2019 - 2024. All rights reserved.