Why can't I create a trait object with let _: Arc<dyn Trait> = value.into()?

試著忘記壹切 提交于 2020-06-16 02:49:24

问题


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
}

Result:

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)

Why does Arc::new(value) compile but not value.into()? I don't understand why Arc<T>::new() is satisfied while From<T>::from isn't.

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

回答1:


There is a fundamental difference in your two lines. The first one:

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

The pattern is not important for the resolution of Arc::new(), since it is defined as you noted:

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

So the type T is deduced from the type of value that is TraitImpl, and an Arc<TraitImpl> is created. Then this type is implicitly unsized-coerced to that Arc<dyn Trait> and all compiles fine.


But the second line is tricker:

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

Since there is not an into function in TraitImpl the compiler searches any trait in scope and finds Into<T>::into(), that is defined as:

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

Now the compiler wonders what type would that T be. Since it is the return of the function, it guesses that T is Arc<dyn Trait>. Now the only interesting implementation of Into is in terms of From:

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

Here X is TraitImpl and T is Arc<dyn Trait>. If you look at the impls of Arc for From, it includes a lot of them, but none that applies. This is the most similar:

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

Then, the compiler shows a few of the failing candidates and emits an error.


The TL;DR; is that you actually want to do two conversions: from TraitImpl to Arc<TraitImpl> and then from Arc<TraitImpl> to Arc<dyn Trait>. But you cannot do both in a single coertion, the compiler must have the intermediate type spelled out somehow.




回答2:


For all generic Rust code there is an implicit Sized bound on any T. This:

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

Is actually this:

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

Which may not always be what you want so it's the only trait you have to explicitly opt-out of like so:

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

So in your case:

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

Is actually:

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

Which is why you can't some_value.into() an Arc<dyn Anything> since all trait objects are unsized.

As to why this restriction exists in the first place we can determine that by looking at the definition of From<T>:

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

from(T) means it has to take some T and put it in the function's call stack, which means T has to have a known size at compile-time and must therefore be Sized.

Update

So this also applies to Arc::new(T) since that function is defined in an impl block like so:

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

And when you call Arc::new(TraitImpl); you are indeed calling it with a Sized type since TraitImpl's size is known at compile-time but then an unsized coercion is triggered by the let variable binding since you ask Rust to treat Arc<TraitImpl> as if it was an Arc<dyn Trait>.

This unsized coercion isn't triggered when you call value.into() since From<T> only takes Sized types.

However, if you are determined to use From<T> you can do so like this:

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
}

In this example you make it clear you're going from a sized type to another sized type, i.e. TraitImpl to Arc<TraitImpl>, before you trigger the unsized coercion Arc<TraitImpl> to Arc<dyn Trait>.

Here are other variations:

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
}


来源:https://stackoverflow.com/questions/61799081/why-cant-i-create-a-trait-object-with-let-arcdyn-trait-value-into

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!