Type coercion for trait objects + auto traits

Hello,

while looking a the implementation of std::error::Error, I noticed the block impl From<String> for Box<dyn Error> that piggy-backs on impl From<String> for Box<dyn Error + Send + Sync>. This block was added due to String, From and Box<Error> interoperation could be better. · Issue #30156 · rust-lang/rust · GitHub.

Here is a toy program that recreates an equivalent problem:

trait Print: std::fmt::Display {
    fn print(&self) {
        println!("{}", self);
    }
}

impl Print for i32 {}

impl From<i32> for Box<dyn Print + Send + Sync> {
    fn from(x: i32) -> Self {
        Box::new(x)
    }
}

// Uncomment the following block to make this program compile.

// impl From<i32> for Box<dyn Print> {
//     fn from(x: i32) -> Self {
//         let x: Box::<dyn Print + Send + Sync> = From::from(x);
//         x
//     }
// }

fn main() {
    let e1: Box<dyn Print + Send + Sync> = From::from(333);
    let e2: Box<dyn Print> = e1;
    e2.print();

    let e3: Box<dyn Print> = From::from(123);
    e3.print();
}

I think I understand why the line creating e3 does not compile without uncommenting the corresponding impl or changing it to

let e3: Box<dyn Print> = Box::<dyn Print + Send + Sync>::from(123);

But isn't the language a bit too strict here? Couldn't it see that although impl From<i32> for Box<dyn Print> is not defined, there exists a conversion to Box<dyn Print + Send + Sync> and that type coerces trivially to the required one? Would that be dangerous/problematic/limiting in any way?

The following discussion may contain the answer, but it's a bit too advanced for me to follow completely:

If the compiler looked for every possible type that could be coerced in your playground, the result would be ambiguous: Box<i32>: From<i32> and coerces to Box<dyn Print> too.

(So one fix is to do:

    let e3: Box<dyn Print> = Box::new(123);

)

Indeed, thanks for the explanation!