为什么不能用let _创建一个特征对象:Arc <dyn Trait> = value.into()?

时间:2020-05-14 13:52:04

标签: rust trait-objects

use std::sync::Arc;

trait Trait {}
struct TraitImpl {}
impl Trait for TraitImpl {}

fn main() {
    let value = TraitImpl {};
    let _: Arc<dyn Trait> = Arc::new(value);    // compiles
    let _: Arc<dyn Trait> = value.into();       // doesn't compile
}

结果:

error[E0277]: the trait bound `std::sync::Arc<dyn Trait>: std::convert::From<TraitImpl>` is not satisfied
  --> src/main.rs:10:35
   |
10 |     let _: Arc<dyn Trait> = value.into();       // doesn't compile
   |                                   ^^^^ the trait `std::convert::From<TraitImpl>` is not implemented for `std::sync::Arc<dyn Trait>`
   |
   = help: the following implementations were found:
             <std::sync::Arc<T> as std::convert::From<T>>
             <std::sync::Arc<T> as std::convert::From<std::boxed::Box<T>>>
             <std::sync::Arc<[T]> as std::convert::From<&[T]>>
             <std::sync::Arc<[T]> as std::convert::From<std::vec::Vec<T>>>
           and 8 others
   = note: required because of the requirements on the impl of `std::convert::Into<std::sync::Arc<dyn Trait>>` for `TraitImpl`

Playground

为什么Arc::new(value)会编译,而value.into()不会编译?我不明白为什么Arc<T>::new()不满意From<T>::from

impl<T> Arc<T> {
    pub fn new(data: T) -> Arc<T>
}
impl<T> From<T> for Arc<T> {
    fn from(t: T) -> Arc<T>
}

2 个答案:

答案 0 :(得分:6)

您的两行内容有根本的区别。第一个:

let _: Arc<dyn Trait> = Arc::new(value);

该模式对于Arc::new()的分辨率并不重要,因为它的定义如您所述:

impl<T> Arc<T> {
    pub fn new(data: T) -> Arc<T>
}

因此,从T的类型value推导类型TraitImpl,并创建了Arc<TraitImpl>。 然后,该类型隐式unsized-coercedArc<dyn Trait>,并且所有编译都可以。


但是第二行比较棘手:

let _: Arc<dyn Trait> = value.into();

由于into中没有TraitImpl函数,编译器将在范围内搜索任何特征并找到Into<T>::into(),其定义为:

pub trait Into<T> {
    fn into(self) -> T;
}

现在,编译器想知道T是什么类型。由于它是函数的返回,因此它猜测TArc<dyn Trait>。现在,Into唯一有趣的实现是根据From

impl<X, T> Into<T> for X where
    T: From<X>

这里XTraitImpl,而TArc<dyn Trait>。如果您将Arc的隐含性视为From,则其中包括很多,但没有一个适用。这是最相似的:

impl<T> From<T> for Arc<T>

然后,编译器会显示一些失败的候选对象并发出错误。


TL; DR; 实际上是您要进行两次转换:从TraitImplArc<TraitImpl>,然后从Arc<TraitImpl>到{{1} }。但是您不能一次执行两个操作,编译器必须以某种方式阐明中间类型。

答案 1 :(得分:2)

对于所有通用Rust代码,在任何Sized上都有一个隐式T绑定。这个:

fn func<T>(t: &T) {}

实际上是这样:

fn func<T: Sized>(t: &T) {}

不一定总是您想要的,所以这是您必须明确选择退出的唯一特征,例如:

fn func<T: ?Sized>(t: &T) {}

所以在您的情况下:

impl<T> From<T> for Arc<T> {
    fn from(t: T) -> Arc<T>
}

实际上是:

impl<T: Sized> From<T> for Arc<T> {
    fn from(t: T) -> Arc<T>
}

这就是为什么无法some_value.into() Arc<dyn Anything>的原因,因为所有特征对象都没有大小。

关于为什么首先存在此限制,我们可以通过查看From<T>的定义来确定:

pub trait From<T> {
    fn from(T) -> Self;
}

from(T)意味着它必须花费一些T并将其放入函数的调用堆栈中,这意味着T在编译时必须具有已知的大小,因此必须为{ {1}}。

更新

因此,这也适用于Sized,因为该函数是在impl块中定义的,如下所示:

Arc::new(T)

当您调用impl<T> for Arc<T> { fn new(T) -> Arc<T> { ... } } 时,您实际上是在使用Arc::new(TraitImpl);类型调用它,因为Sized的大小在编译时是已知的,但是没有大小限制的强制TraitImpl变量绑定触发,因为您要求Rust将let视为Arc<TraitImpl>

调用Arc<dyn Trait>时不会触发这种大小不等的强制,因为value.into()仅接受From<T>类型。

但是,如果您决定使用Sized,您可以这样做:

From<T>

在此示例中,您清楚地说明了从大小类型转换为另一种类型,即从use std::sync::Arc; trait Trait {} struct TraitImpl {} impl Trait for TraitImpl {} fn main() { let value = TraitImpl {}; let _: Arc<dyn Trait> = Arc::new(value); // compiles let value = TraitImpl {}; let _: Arc<dyn Trait> = <Arc<TraitImpl>>::from(value); // also compiles } TraitImpl,然后再将未强制大小的Arc<TraitImpl>触发为{{ 1}}。

还有其他变种:

Arc<TraitImpl>