【问题标题】:How does the mechanism behind the creation of boxed traits work?创建盒装特征背后的机制如何运作?
【发布时间】:2019-02-16 17:30:06
【问题描述】:

我无法理解盒装特征的值是如何产生的。考虑以下代码:

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::<dyn Fooer>::new(32);           // doesn't work
    let d: Box<dyn Fooer> = Box::new(32);        // works, creates a Box<Fooer>
    let e: Box<dyn Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
}

显然,变体 a 和 b 可以正常工作。但是,变体 c 没有,可能是因为 new 函数只接受相同类型的值,而自 Fooer != i32 以来情况并非如此。变体 d 和 e 工作,这让我怀疑正在执行某种从 Box&lt;i32&gt; 到 Box&lt;dyn Fooer&gt; 的自动转换。

所以我的问题是:

  • 这里是否发生了某种转换?
  • 如果是这样,它背后的机制是什么,它是如何工作的? (我也对底层细节感兴趣,即在幕后如何表示东西)
  • 有没有办法直接从i32 创建Box&lt;dyn Fooer&gt;?如果没有:为什么不呢?

【问题讨论】:

  • 我问自己,你为什么想要Box&lt;Fooer&gt;。我想你不太明白,特征在 Rust 中是如何工作的。尝试在每个变量 (a..e) 上调用 .foo(),看看会发生什么。
  • 编译器告诉你确切的原因,为什么c不起作用:the method 'new' exists but the following trait bounds were not satisfied: 'Fooer : std::marker::Sized'
  • 这是另一个关于方差的问题。我很抱歉,但我无能为力:这种事情仍然会触发我:P
  • @hellow:关于错误消息:这似乎暗示它实际上无法调用Box::&lt;Fooer&gt;::new,因为给定的类型参数必须具有在编译时已知的大小,而Fooer 之后的情况并非如此是一种特质,对吧?所以我的假设是正确的,你必须先构造一个大小类型的 Box,然后以某种方式将其转换为 trait 类型的 Box,这会给我留下我最初的问题:这种转换是如何/通过什么机制发生的?
  • @Boiethios AFAIU Variance 仅限于 Rust 的生命周期,因为 trait 和实现结构之间没有子类型关系。没有“结构继承”。否则,鉴于Box&lt;T&gt; 是T 的变体,我可以将Box&lt;Box&lt;i32&gt;&gt; 转换为Box&lt;Box&lt;Fooer&gt;&gt;。另一方面,将Box&lt;Box&lt;&amp;'a i32&gt;&gt; 转换为Box&lt;Box&lt;&amp;'b Fooer&gt;&gt; 其中'a : 'b 是可能的,因为'a : 'b 与i32: Fooer 不同,是实际的子类型关系,并且不可变借用&amp;'x T 不仅是T 的变体,而且也超过'x。

标签: rust polymorphism traits boxing representation


【解决方案1】:

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

有一个名为 Unsize 的标记特征,除其他外:

Unsize 用于:

  • 当T: Trait 时T 是Unsize&lt;Trait&gt;。
  • [...]

这个特性,AFAIK,不直接用于强制。而是使用CoerceUnsized。这个特性在很多情况下都会实现,其中一些是相当预期的,例如:

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

用于将&amp;i32 强制转换为&amp;Fooer。

影响您的代码的这个特征的有趣但不那么明显的实现是:

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

这与Unsize 标记的定义一起可以理解为:如果U 是一个特征并且T 实现U,那么Box&lt;T&gt; 可以强制转换为@ 987654337@.

关于你的最后一个问题:

有没有办法直接从i32 创建Box&lt;Fooer&gt;?如果没有:为什么不呢?

我不知道。问题是Box::new(T) 需要一个有大小的值,因为传递的值被移动到盒子里,而没有大小的值不能被移动。

在我看来,最简单的方法就是简单地写:

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

也就是说,您创建一个正确类型的 Box,然后强制转换为未调整大小的那个(注意它看起来与您的 d 示例非常相似)。

【讨论】:

    【解决方案2】:

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

    不,这是因为Box&lt;dyn Fooer&gt; 没有new 函数。在documentation:

    impl&lt;T&gt; Box&lt;T&gt;

    pub fn new(x: T) -&gt; Box&lt;T&gt;

    Box&lt;T&gt; 上的大多数方法都允许T: ?Sized,但new 是在impl 中定义的没有T: ?Sized 绑定。 means 只能在 T 是已知大小的类型时调用 Box::&lt;T&gt;::new。 dyn Fooer 没有大小,所以根本没有要调用的 new 函数。

    事实上,这个函数不能存在于今天的 Rust 中。 Box&lt;T&gt;::new 需要知道具体类型 T 以便它可以分配正确大小和对齐的内存。因此,您不能删除T之前您将其发送到Box::new。 (可以想象,future language extensions 可能允许函数接受未调整大小的参数;但是,即使是 unsized_locals 是否真的会让Box&lt;T&gt;::new 接受未调整大小的T,也不清楚。)

    目前,像dyn Fooer这样的unsized类型只能存在于一个“胖指针”后面,即一个指向对象的指针和一个指向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&lt;i32&gt;,然后是coerced 到Box&lt;Fooer&gt;。您可以认为这是一种转换,但 Box 没有改变;编译器所做的只是在它上面加上一个额外的指针并忘记它的原始类型。 rodrigo's answer 更详细地介绍了这种强制转换的语言级机制。

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

    有没有办法直接从i32 创建Box&lt;Fooer&gt;?

    是“否”:i32 必须被装箱在你可以删除它的类型。这也是你不能写let x: Fooer = 10i32的原因。

    相关

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-01
      • 2017-05-23
      • 1970-01-01
      相关资源
      最近更新 更多