Vector 如何返回添加到trait对象向量的具体类型的引用?
在这段代码中,我获取一个向量,创建一个结构实例,并将其添加到已装箱的向量中:Vector 如何返回添加到trait对象向量的具体类型的引用?,vector,rust,Vector,Rust,在这段代码中,我获取一个向量,创建一个结构实例,并将其添加到已装箱的向量中: 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::tran
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()) }
}
我认为该代码是安全的:
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 }
}
fn添加指令(向量:&mut向量)->&X{
设x=x{};
设b=Box::new(x);
设ptr=&*b为*常数X;
矢量推力(b);
不安全{&*ptr}
}
诀窍是将原始指针保存到*const X
,然后再将其转换为框。然后,您可以在从函数返回之前将其转换回引用。
它是安全的,因为装箱的值永远不会被移动(当然,除非它从框中移出),所以ptr
在b
到框中后仍然存在,我认为该代码是安全的:
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 }
}
fn添加指令(向量:&mut向量)->&X{
设x=x{};
设b=Box::new(x);
设ptr=&*b为*常数X;
矢量推力(b);
不安全{&*ptr}
}
诀窍是将原始指针保存到*const X
,然后再将其转换为框。然后,您可以在从函数返回之前将其转换回引用。
它是安全的,因为装箱的值永远不会被移动(当然,除非它从框中移出),所以ptr
在b
到框中的过程中幸存下来,而b
您的“丑陋的黑客”实际上是完全不正确和不安全的。您很不幸,Rust 1.32没有报告错误,但幸运的是Rust 1.34报告了错误
存储装箱值时,将创建一个精简指针。这占用了整数的平台本机大小(例如32位x86上的32位、64位x86上的64位等):
+----------+
|指针|
|(0x1000)|
+----------+
存储装箱的trait对象时,会创建一个胖指针。它包含指向数据的相同指针和对vtable的引用。此指针的大小为两个本机整数:
+----------+----------+
|指针| vtable|
|(0x1000)|(0x1000)|
+----------+----------+
通过尝试执行从trait对象到引用的转换,您将丢失其中一个指针,但未定义哪个指针。无法保证哪个先到:数据指针还是vtable
可以使用一种解决方案,但这是不稳定的,因为fat指针的布局仍然悬而未决
我建议的解决方案是使用Any
,它不需要safe
代码:
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()
}
另见:
你的“丑陋的黑客”实际上是完全不正确和不安全的。您很不幸,Rust 1.32没有报告错误,但幸运的是Rust 1.34报告了错误
存储装箱值时,将创建一个精简指针。这占用了整数的平台本机大小(例如32位x86上的32位、64位x86上的64位等):
+----------+
|指针|
|(0x1000)|
+----------+
存储装箱的trait对象时,会创建一个胖指针。它包含指向数据的相同指针和对vtable的引用。此指针的大小为两个本机整数:
+----------+----------+
|指针| vtable|
|(0x1000)|(0x1000)|
+----------+----------+
通过尝试执行从trait对象到引用的转换,您将丢失其中一个指针,但未定义哪个指针。无法保证哪个先到:数据指针还是vtable
可以使用一种解决方案,但这是不稳定的,因为fat指针的布局仍然悬而未决
我建议的解决方案是使用Any
,它不需要safe
代码:
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()
}
另见:
@Shepmaster奇怪,它在rustc1.32.0上为我编译,这就是我的代码。除了我的向量被另一个向量包装外,我没有做任何更改。我的评论是在你编辑评论以包含版本之前。该版本是MCVE的一部分;我已经对你的问题进行了编辑,以包含相关的细节。我们鼓励你开始使用dyn
来表示特质对象:为什么你首先需要获得对具体类型的引用?你能通过trait来公开必要的行为吗,这样你就可以简单地使用trait对象了?@Shepmaster奇怪,它在rustc1.32.0
@Shepmaster上为我编译,这就是我的代码。除了我的向量被另一个向量包装外,我没有做任何更改。我的评论是在你编辑评论以包含版本之前。该版本是MCVE的一部分;我已经对你的问题进行了编辑,以包含相关的细节。我们鼓励你开始使用dyn
来表示特质对象:为什么你首先需要获得对具体类型的引用?你能通过特质来展示必要的行为吗,因此,您可以简单地使用trait对象吗?您能否指出,从装箱值创建trait对象不会移动/复制/使指针无效的任何保证?@Shepmaster:我没有任何直接引用,说明在向trait对象强制转换时,指向装箱值的指针不会无效。但从我的理解来看,我希望从盒子和分配器的工作方式来看这是真的。我的代码也可以使用它,我将其解释为该代码确实安全的证据。@Shepmaster:再看一点,Box
通过trait转换为Box
,它没有函数成员,因此强制是由编译器自动完成的,不可能重新分配。还要注意的是,这也适用于Rc
和Arc
,这里的对象不能移动。我当然希望这样的转换不能做任何事情,但在我看来,对不安全代码有额外的确定性始终是正确的。你能指出