盒装特征创造背后的机制如何运作?

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

我无法理解盒装特征的价值是如何形成的。请考虑以下代码:

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?如果没有:为什么不呢?
rust polymorphism traits boxing representation
2个回答
8
投票

但是,变量c没有,可能是因为new函数只采用相同类型的值,而自Fooer != i32以来并非如此。

不,这是因为new没有Box<dyn Fooer>功能。在documentation

impl<T> Box<T>

pub fn new(x: T) -> Box<T>

Box<T>上的大多数方法都允许使用T: ?Sized,但new是在没有impl约束的T: ?Sized中定义的。 means你只能在Box::<T>::new是一个已知大小的类型时调用Tdyn Fooer未经过调整,因此根本没有new方法可以调用。

实际上,那种方法不可能存在。为了装箱,你需要知道它的大小。为了将它传递给函数,您需要知道它的大小。为了甚至包含一个包含某些内容的变量,它必须具有一个大小。像dyn Fooer这样的非类型类型只能存在于“胖指针”后面,即指向该对象的指针和指向该对象的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返回一个Box<i32>,然后是coercedBox<Fooer>。你可以认为这是一个转换,但Box没有改变;所有编译器都会在其上粘贴一个额外的指针并忘记它的原始类型。 rodrigo's answer详细介绍了这种胁迫的语言层面的机制。

希望所有这一切都可以解释为什么答案

有没有办法直接从Box<Fooer>创建i32

是“不”:i32必须先装箱才能擦掉它的类型。这是你不能写let x: Fooer = 10i32的原因。

Related


5
投票

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

有一个名为Unsize的标记特征,在其他之间:

Unsize实现为:

  • T时,Unsize<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例子)。

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