【问题标题】:Why can't I create a trait object with let _: Arc<dyn Trait> = value.into()?为什么我不能用 let _: Arc<dyn Trait> = value.into() 创建一个 trait 对象?
【发布时间】:2020-05-14 13:52:04
【问题描述】:
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&lt;T&gt;::new() 满意而From&lt;T&gt;::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>
}

【问题讨论】:

    标签: generics rust traits trait-objects


    【解决方案1】:

    你的两条线有根本的区别。第一个:

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

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

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

    所以T 的类型是从value 的类型推导出来的,即TraitImpl,并创建了一个Arc&lt;TraitImpl&gt;。 那么这个类型隐含地是unsized-coerced 到那个Arc&lt;dyn Trait&gt; 并且所有编译都很好。


    但是第二行比较诡异:

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

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

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

    现在编译器想知道T 是什么类型。由于是函数的返回,所以猜测T就是Arc&lt;dyn Trait&gt;。现在Into 唯一有趣的实现是From

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

    这里XTraitImplTArc&lt;dyn Trait&gt;。如果您查看 ArcFrom 的 impls,它包含了很多,但没有一个适用。这是最相似的:

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

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


    TL;DR; 是您实际上想要进行两次转换:从TraitImplArc&lt;TraitImpl&gt;,然后从Arc&lt;TraitImpl&gt;Arc&lt;dyn Trait&gt;。但是你不能同时做这两个,编译器必须以某种方式拼写出中间类型。

    【讨论】:

      【解决方案2】:

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

      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&lt;dyn Anything&gt; 因为所有 trait 对象都没有大小。

      至于为什么首先存在这个限制,我们可以通过查看From&lt;T&gt;的定义来确定:

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

      from(T) 意味着它必须取一些T 并将其放入函数的调用堆栈中,这意味着T 在编译时必须具有已知大小,因此必须是Sized

      更新

      所以这也适用于Arc::new(T),因为该函数是在 impl 块中定义的,如下所示:

      impl<T> for Arc<T> {
          fn new(T) -> Arc<T> {
              ...
          }
      }
      

      当你调用Arc::new(TraitImpl); 时,你确实是用Sized 类型调用它,因为TraitImpl 的大小在编译时是已知的,但是一个未调整大小的强制 是由let 变量绑定,因为您要求 Rust 将 Arc&lt;TraitImpl&gt; 视为 Arc&lt;dyn Trait&gt;

      当您调用 value.into() 时,不会触发这种未调整大小的强制转换,因为 From&lt;T&gt; 只接受 Sized 类型。

      但是,如果您决定使用From&lt;T&gt;,您可以这样做:

      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
      }
      

      在此示例中,您清楚地表明您将从一个大小类型转换为另一个大小类型,即 TraitImplArc&lt;TraitImpl&gt;,然后触发未调整大小的强制转换 Arc&lt;TraitImpl&gt;Arc&lt;dyn Trait&gt;

      以下是其他变体:

      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); // compiles
      
          let value = TraitImpl {};
          let _: Arc<dyn Trait> = Arc::from(value); // compiles, can infer Arc<TraitImpl> here
      
          let value = TraitImpl {};
          let _: Arc<dyn Trait> = Into::<Arc<TraitImpl>>::into(value); // compiles
      
          let value = TraitImpl {};
          let _: Arc<dyn Trait> = Into::<Arc<_>>::into(value); // compiles, can infer Arc<TraitImpl> here
      
          let value = TraitImpl {};
          let _: Arc<dyn Trait> = Into::into(value); // doesn't compile, infers Arc<dyn Trait> here
      }
      

      【讨论】:

      • 不应该也适用于impl&lt;T&gt; Arc&lt;T&gt;吗?
      • 嗯。 struct ArcT: ?Sized,但 impl&lt;T&gt; 块没有。那不是说impl&lt;T&gt; 就是impl&lt;T: Sized&gt;
      • @JohnKugelman 是对的,fn new 是在 impl&lt;T&gt; Arc&lt;T&gt; 块中定义的,没有 ?Sized 绑定。它起作用的原因是因为Arc::new(value) 返回一个Arc&lt;TraitImpl&gt;,它可以强制转换为Arc&lt;dyn Trait&gt;value.into() 使用类型推断来确定 .into() 必须返回的内容并因为没有 impl From&lt;TraitImpl&gt; for Arc&lt;dyn Trait&gt; 而感到困惑。
      • Arc::from(value) 也适用于没有明确的TraitImpl。我相信这是因为提及Arc 暗示编译器应该推断类型参数here,而不是简单地从= 的左侧复制它。稍微阐述一下,Into::&lt;Arc&lt;_&gt;&gt;::into(value) 有效,但 Into::&lt;_&gt;::into(value) 无效。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-10
      相关资源
      最近更新 更多