盒装特质创建背后的机制如何运作?

时间:2018-09-12 06:45:24

标签: rust polymorphism traits boxing representation

我很难理解框式特征的价值是如何形成的。考虑以下代码:

trait Fooer {
    fn foo(&self);
}

impl Fooer for i32 {
    fn foo(&self) { println!("Fooer on i32!"); }
}

fn main() {
    let a = Box::new(32);                    // works, creates a Box<i32>
    let b = Box::<i32>::new(32);             // works, creates a Box<i32>
    let c = Box::<Fooer>::new(32);           // doesn't work
    let d: Box<Fooer> = Box::new(32);        // works, creates a Box<Fooer>
    let e: Box<Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
}

很明显,变体a和b非常有用。但是,变体c没有这样做,可能是因为new函数仅采用相同类型的值,而自Fooer != i32以来就不是这种情况。 d和e的变体,使我怀疑正在执行从Box<i32>Box<Fooer>的某种自动转换。

所以我的问题是:

  • 这里会发生某种转换吗?
  • 如果是这样,其背后的机制是什么以及如何起作用? (我也对低级细节感兴趣,即引擎盖下的东西如何表示)
  • 是否可以直接从Box<Fooer>创建i32?如果没有:为什么不呢?

2 个答案:

答案 0 :(得分:8)

  

但是,变体c却没有,可能是因为new函数仅采用相同类型的值,而自Fooer != i32之后就没有这种情况。

不,这是因为new没有{em> 功能。在documentation中:

  

Box<dyn Fooer>

     impl<T> Box<T>

pub fn new(x: T) -> Box<T>上的大多数方法都允许Box<T>,但是T: ?Sized是在new 中定义的,没有约束implmeans仅在T: ?Sized是已知大小的类型时才可以调用Box::<T>::newT没有大小,因此根本没有dyn Fooer的调用方法。

实际上,该方法不存在。为了装箱,您需要知道它的大小。为了将其传递给函数,您需要知道其大小。为了使变量包含某些内容,它必须具有大小。像new这样的未定义类型只能存在于“胖指针”之后,即指向对象的指针和指向该对象的dyn Fooer实现的指针。 / p>

您如何获得胖指针?您从细指针开始,然后强制。这就是这两行中发生的事情:

Fooer

let d: Box<Fooer> = Box::new(32); // works, creates a Box<Fooer> let e: Box<Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer> 返回一个Box::new,然后它是coercedBox<i32>。您可以认为这是一次转换,但是Box<Fooer>并未更改;编译器所做的全部工作就是在其上附加一个指针,然后忘记其原始类型。 rodrigo's answer详细介绍了这种强制的语言级机制。

希望所有这些都能解释为什么答案

  

是否可以直接从Box创建Box<Fooer>

为“否”:必须先将i32装箱,然后才能清除其类型。这是您不能写i32的相同原因。

相关

答案 1 :(得分:5)

我将尝试解释您的代码中发生了哪些转换(强制)。

之间有一个名为Unsize的标记特征:

  

未调整大小适用于:

     
      
  • TUnsize<Trait>时的T: Trait
  •   
  • [...]
  •   

此特征AFAIK不直接用于强制。而是使用CoerceUnsized。在许多情况下都可以实现此特征,其中有些是可以预期的,例如:

impl<'a, 'b, T, U> CoerceUnsized<&'a U> for &'b T 
where
    'b: 'a,
    T: Unsize<U> + ?Sized,
    U: ?Sized

用于将&i32强制转换为&Fooer

此特征有趣但不太明显的实现会影响您的代码:

impl<T, U> CoerceUnsized<Box<U>> for Box<T> 
where
    T: Unsize<U> + ?Sized,
    U: ?Sized

这与Unsize标记的定义可以理解为:如果U是一个特征并且T实现了U,则Box<T>可以强制进入Box<U>

关于您的最后一个问题:

  

是否可以直接从Box<Fooer>创建i32?如果不是:为什么不呢?

我不知道。问题在于Box::new(T)要求使用大小值,因为传递的值已移动到框中,并且未移动的值无法移动。

我认为,最简单的方法是简单地编写:

let c = Box::new(42) as Box<Fooer>;

也就是说,您创建了一个正确类型的Box,然后强制使用未缩放的大小(请注意,它看起来与您的d示例非常相似)。