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);
}
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).
This is deref coercion (via the blanket impl<'a, T> Deref for &'a There), 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.)