在这段代码中,我使用一个向量,创建一个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)
你的“丑陋的黑客”实际上是完全不正确和不安全的。你不幸的是,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
}
}
也可以看看:
我认为这段代码是安全的:
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>
。