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奇怪,它在rustc
      1.32.0上为我编译,这就是我的代码。除了我的向量被另一个向量包装外,我没有做任何更改。我的评论是在你编辑评论以包含版本之前。该版本是MCVE的一部分;我已经对你的问题进行了编辑,以包含相关的细节。我们鼓励你开始使用
      dyn
      来表示特质对象:为什么你首先需要获得对具体类型的引用?你能通过trait来公开必要的行为吗,这样你就可以简单地使用trait对象了?@Shepmaster奇怪,它在rustc
      1.32.0
      @Shepmaster上为我编译,这就是我的代码。除了我的向量被另一个向量包装外,我没有做任何更改。我的评论是在你编辑评论以包含版本之前。该版本是MCVE的一部分;我已经对你的问题进行了编辑,以包含相关的细节。我们鼓励你开始使用
      dyn
      来表示特质对象:为什么你首先需要获得对具体类型的引用?你能通过特质来展示必要的行为吗,因此,您可以简单地使用trait对象吗?您能否指出,从装箱值创建trait对象不会移动/复制/使指针无效的任何保证?@Shepmaster:我没有任何直接引用,说明在向trait对象强制转换时,指向装箱值的指针不会无效。但从我的理解来看,我希望从盒子和分配器的工作方式来看这是真的。我的代码也可以使用它,我将其解释为该代码确实安全的证据。@Shepmaster:再看一点,
      Box
      通过trait转换为
      Box
      ,它没有函数成员,因此强制是由编译器自动完成的,不可能重新分配。还要注意的是,这也适用于
      Rc
      Arc
      ,这里的对象不能移动。我当然希望这样的转换不能做任何事情,但在我看来,对不安全代码有额外的确定性始终是正确的。你能指出