3
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>
}
pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
John Kugelman
  • 349,597
  • 67
  • 533
  • 578

2 Answers2

9

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.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
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
}
pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
  • 1
    Shouldn't that apply to `impl Arc` too? – John Kugelman May 14 '20 at 14:07
  • @JohnKugelman it does not apply to Arc, I've updated my answer to explain why, but here's also a link to [Arc's source](https://doc.rust-lang.org/stable/src/alloc/sync.rs.html#196-199) if you wanna see for yourself. – pretzelhammer May 14 '20 at 14:11
  • Hm. `struct Arc` has `T: ?Sized`, but the `impl` block doesn't. Doesn't that mean `impl` is `impl`? – John Kugelman May 14 '20 at 14:14
  • 2
    @JohnKugelman is right, `fn new` is defined in an `impl Arc` block with no `?Sized` bound. The reason it works is because `Arc::new(value)` returns an `Arc` which can be coerced to `Arc`. `value.into()` uses type inference to determine what `.into()` must return and gets confused because there is no `impl From for Arc`. – trent May 14 '20 at 14:17
  • Right, when you call `Arc::new(TraitImpl);` at the time of that call `TraitImpl` is indeed `Sized` but then is coerced to the unsized type `Arc` in the `let` variable binding. – pretzelhammer May 14 '20 at 14:21
  • @JohnKugelman I've updated my answer to be more thorough and also mention unsized coercions, I hope it's now satisfactory. – pretzelhammer May 14 '20 at 14:36
  • What is an unsized coercion? – John Kugelman May 14 '20 at 14:42
  • @JohnKugelman it's when you coerce / cast a sized type to an unsized type, like in the case of coercing / casting `Arc` to `Arc`. – pretzelhammer May 14 '20 at 14:48
  • 1
    `Arc::from(value)` also works with no explicit `TraitImpl`. I believe this is because mentioning `Arc` hints to the compiler that it should infer the type parameter *here* and not simply copy it from the left hand side of the `=`. Elaborating a little, `Into::>::into(value)` works, but `Into::<_>::into(value)` does not. – trent May 14 '20 at 15:22