I read What are Rust's exact auto-dereferencing rules? from beginning to end, but I still have a question about the coercion from array to slice.
Let us think about the following code:
let arr: &[i32; 5] = &&&[1, 2, 3, 4, 5];
// let arr: &[i32] = &&&[1, 2, 3, 4, 5]; // Error; expected slice, found reference
I would expect that &&&[1, 2, 3, 4, 5]
has the type, &&&[i32; 5]
and dereferences to &&[i32; 5]
=> &[i32; 5]
=> &[i32; 5]
=> &[i32]
,
but the result is different from what I expected.
I tried to run the following code:
let arr: &&&[i32; 5] = &&&[1, 2, 3, 4, 5];
let n = arr.first().unwrap(); // 1
That's the correct code. The type of arr
is coerced to &&&[i32; 5]
=> &&[i32; 5]
=> &[i32; 5]
=> &[i32]
and matches to the first argument of first
in slice, &self
.
What's the condition that arrays coerce to slices? I don't understand the difference between the former and the latter code.
I also checked the documentation in the source code, and guess that the above question has something to do with the sentence cited below;
However we sometimes do other adjustments and coercions along the way, in particular unsizing (e.g., converting from [T; n] to [T]).`
This kind of coercion is intended to work, but not implemented.
Arrays do not implement Deref
, so the coercion &[T; n] -> &[T]
is not a deref coercion and does not work in quite the same way as one. Instead, it's called an "unsized coercion" because it turns a sized type ([T; n]
) into an unsized one ([T]
).
That said, the language reference (which is not normative and may be outdated, but bear with me) lists the possible coercions, including the following (emphasis added):
T_1
toT_3
whereT_1
coerces toT_2
andT_2
coerces toT_3
(transitive case)Note that this is not fully supported yet.
&T
or&mut T
to&U
ifT
implementsDeref<Target = U>
.TyCtor(
T
) to TyCtor(U
), where TyCtor(T
) is one of
&T
&mut T
*const T
*mut T
Box<T>
and where
U
can be obtained fromT
by unsized coercion.
The last bullet, unsized coercion, is what allows &[T; n]
to coerce to &[T]
. Notably, this only describes one layer of referencing; it doesn't cover the &&[T; n]
-> &[T]
case (for which we also need Deref
coercion).
Back to your non-working example:
let arr: &[i32] = &&&[1, 2, 3, 4, 5];
The intended coercion is &&&[i32; 5]
-> &[i32]
. We can work out how this coercion ought to work:
&[i32; 5]
coerces to &[i32]
by unsizing;&&[i32; 5]
coerces to &[i32; 5]
by Deref
;&&[i32; 5]
coerces to &[i32]
by transitivity.&&&[i32; 5]
coerces to &&[i32; 5]
by Deref
;&&&[i32; 5]
coerces to &[i32]
by transitivity.But it doesn't. The quote above hints at why: under the transitive case, it says "Note that this is not fully supported yet". As far as I can tell, according to issue #18602, "not fully supported" is a hedge; it would be more accurate to say "unimplemented". So, for now, coercion via transitivity is not possible at all. Apparently this issue is not a high priority, probably because sized arrays aren't very common. (I suspect this might become a more common complaint when const generics land, since that may make arrays more useful.)
So why does arr.first()
work? Well, the "auto-dereferencing rules" used to find methods invoked with the .
(dot) operator are different from the coercion rules. Autoderef is similar to manually dereferencing any number of times until you get something (that can be coerced to a type) with the given method. This means you don't need transitive coercion to find method calls through autoderef.
RFC #401 describes intended semantics of most coercions, but has never been fully implemented. Issue #18602 tracks the status of transitive coercion.
The Rustonomicon also has a chapter on coercions and appears to agree with the reference book.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With