Why does unsize coercion cannot be performed for a user-defined type in a function call?

Consider this example

struct D<T:?Sized>{
	i:Box<T>
}
trait A{
	fn show(&self){
		println!("trait A");
	}
}
impl A for i32{
	fn show(&self){
		println!("i32");
	}
}
fn test(_:D<dyn A>){} 

fn test2(v:Box<dyn A>){}

fn main(){
   let c = D{i:Box::new(1)};
   let coerced:D<dyn A> = D{i:Box::new(1)}; // ok
   test(coerced); // ok
   test(c); // #1 error
   let c1 = Box::new(1);
   let coerced_1:Box<dyn A> = Box::new(1); // ok
   test2(c1); // #2 ok
   test2(coerced_1); // ok
   test(D{i:Box::new(1)}); // #3 ok
}

First, according to Type coercions - The Rust Reference, which says:

T to dyn U, when T implements U + Sized, and U is object safe.

Foo<..., T, ...> to Foo<..., U, ...>, when:

  • Foo is a struct.
  • T implements Unsize<U>.
  • The last field of Foo has a type involving T.
  • If that field has type Bar<T>, then Bar<T> implements Unsized<Bar<U>>.
  • T is not part of the type of any other fields.

i32 can be coerced to dyn A because i32 implements A + Sized,

The following coercions are built-ins and, if T can be coerced to U with one of them, then an implementation of Unsize<U> for T will be provided:

So Unsize<dyn A> for i32 will be provided. So, all bullets are satisfied for D<i32> and D<dyn A>. As the contrast, #1 is an error while #2 is ok. We must first coerce D<i32> to D<dyn A> and use the result to call test, or we should directly use D{i:Box::new(1)} as the argument to call test. Unlike Box, we can use it to call test2 in any way.

Why the argument of type D<i32> cannot be coerced to D<dyn A> in the function call like that Box<i32> is coerced to Box<dyn A>?

i32 implements Unsize<dyn A>, but the last field has type Box<T>, and Box<i32> does not implement Unsize<Box<dyn A>>. (Instead, it implements CoerceUnsized<Box<dyn A>>.) If you want to coerce your user-defined type to an unsized value, it must store the T by-value, and the whole object must be placed behind a Box or similar (Rust Playground):

struct D<T: ?Sized> {
    i: T,
}

trait A {
    fn show(&self) {
        println!("trait A");
    }
}

impl A for i32 {
    fn show(&self) {
        println!("i32");
    }
}

fn main() {
    let c: Box<D<i32>> = Box::new(D { i: 1 });
    let coerced: Box<D<dyn A>> = c; // ok
    coerced.i.show(); // ok
}

See Unsize in std::marker - Rust

Structs Foo<..., T, ...> implement Unsize<Foo<..., U, ...>> if all of these conditions are met:

Box<i32> and Box<dyn A> is that case. If D<i32> couldn't be coerced to D<dny A> as you said, then let coerced:D<dyn A> = D{i:Box::new(1)}; would be an error.

Box<T> is only allowed to do that since it explicitly implements the unstable CoerceUnsized trait.

In your D<dyn A> example, the D<i32> is not actually coerced to a D<dyn A>. Instead, the Box<i32> is coerced to a Box<dyn A> before it is written to the i field. To illustrate, with your original definitions (Rust Playground):

fn main() {
    // ok:
    {
        let b: Box<i32> = Box::new(1);
        let b_coerced: Box<dyn A> = b;
        let coerced: D<dyn A> = D { i: b_coerced };
    }
    // error:
    {
        let b: Box<i32> = Box::new(1);
        let c: D<i32> = D { i: b };
        let coerced: D<dyn A> = c;
    }
}

This statement desugars into
let coerced:D<dyn A> = D::<dyn A>{i:Box::new(1)}
not
let coerced:D<dyn A> = D::<i32>{i:Box::new(1)}

Seems Box<T> does not implement Unsize<Box<U>> even though T: Unsize<U> because T is not the last field involved T

pub struct Box<
    T: ?Sized,
    #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
>(Unique<T>, A);

Another thing I think I misunderstand is:

T implements Usize<U> if and only if T and U is one of the specified types in the list

However, why this example is still not compiled?

struct D<'a,T:?Sized>{
	i:&'a T
}
fn main(){
  	let cc:D<i32> = D{i:&0};
	let ccc:D<dyn A> = cc;
}

i32: Unsize<dny A>, and

  • The last field of D has a type involving T.

You are looking at &i32 and &dyn A, and &i32: !Unsize<&dyn A>. It's no different from the Box case.

I seem to know the confusing here.

If that field has type Bar<T>, then Bar<T> implements Unsized<Bar<U>>.

Is & (mut)opt T considered to be Bar<T>? I thought Bar<T> means Bar is a generic struct type.

Yes, Bar<T> can be any family of types with a type parameter. We could even write it as a type alias:

type Bar<T> = &'static T;
// or
type Bar<T> = &'static mut T;
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.