Why need to explicitly coerce array into slice

Hello,

I'm curious why, in my code block below, the rust compiler requires the slice to be coerced explicitly into an array.

The code segment below compiles and runs fine in the playground, however if you un-comment the line:

    //let foos = &[&FOOS];      // using this line instead of the one below causes compiler error

(and remove the one above it.)

You get the following compiler error

   |
33 |     let bar = Bar::new(&foos);
   |                        ^^^^^ expected slice, found `&[&[Foo; 2]; 1]`
   |
   = note: expected reference `&[&[Foo]]`
              found reference `&&[&[Foo; 2]; 1]`

My question is, why does this error occur? It seems that the compiler should be able to infer the slice type based on the signature of the new() function call.

#[derive(Debug)]
struct Foo {
    num: i32,
}

#[derive(Debug)]
struct Bar<'a> {
    foos: &'a [&'a [Foo]],
}
impl Bar<'_> {
    fn new<'a>(
        foos: &'a [&'a [Foo]],
    ) -> Bar<'a> {
        Bar {
            foos
        }
    }
}

static FOOS: [Foo;2] = [
    Foo {
        num: 98,
    },
    Foo {
        num: 150,
    },
];

fn main() {
    let foos: &[&[Foo]] = &[&FOOS];
    //let foos = &[&FOOS];      // using this line instead of the one above causes compiler error

    let bar = Bar::new(&foos);
    println!("bar = {:?}", bar);
}
1 Like

Unsizing coercion only takes place on the right-hand side of a let statement when a type is provided:

https://doc.rust-lang.org/stable/reference/type-coercions.html#coercion-sites

I imagine this restriction exists to limit the non-local effect of coercions (the expression or type declaration that triggers the coercion could be arbitrarily far away from the let statement).

1 Like

I see. Thank you very much for the answer and the reference.

Once again, rust has covered things very well.

I have another question related to this code block. I should probably create a different topic, and if so please let me know and I will.

But my question is why is the & optional on the call to new?

In other words, the reference indicator is apparently not necessary here. Which seems counter to other times when in fact it is required.

 let bar = Bar::new(&foos);

yields same results as :

 let bar = Bar::new(foos);

This is deref coercion (via the blanket impl<'a, T> Deref for &'a T here), and it happens because the argument of a function call expression is a potential coercion site.

That raises the question: why can't the compiler perform unsizing coercion at the same site, to take you from &[&[Foo; 2]; 1] to &[&[Foo]]? I'm not sure. It might be related to this note in the same page of the Reference:

  • T_1 to T_3 where T_1 coerces to T_2 and T_2 coerces to T_3 (transitive case)

    Note that this is not fully supported yet.

The coercion from &[&[T; 2]; 1] to &[&[T]] does conceptually involve two steps, passing through either &[&[T; 2]] or &[&[T]; 1], so maybe this is one of the "transitive cases" that the compiler just can't see through yet.

Edit: ignore that, the reason is simply that there is no general deref coercion from &[&[T; M]; N] to &[&[T]; N]—the coercion from &[T; M] to &T only applies if you write out a subexpression of type &[T; M] that is also a coercion site. (Note that [&[T; M]; N] and [&[T]; N] do not have the same layout, because &[T; M] is a thin pointer and &[T] is a fat pointer, so the conversion between them is not trivial.)

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.