6

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]).`

Shepmaster
  • 274,917
  • 47
  • 731
  • 969

1 Answers1

8

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 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

  • &T to &U if T implements Deref<Target = U>.

  • TyCtor(T) to TyCtor(U), where TyCtor(T) is one of

    • &T
    • &mut T
    • *const T
    • *mut T
    • Box<T>

    and where T can be obtained from U 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:

  1. &[i32; 5] coerces to &[i32] by unsizing;
  2. &&[i32; 5] coerces to &[i32; 5] by Deref;
  3. therefore, &&[i32; 5] coerces to &[i32] by transitivity.
  4. &&&[i32; 5] coerces to &&[i32; 5] by Deref;
  5. therefore, &&&[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 an extension of 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 transitivity to find method calls through autoderef (which RFC 401 calls "receiver coercion").


Further reading

RFC #401 describes intended semantics of most coercions. This RFC was merged over 5 years ago. Many things have changed since then, but it is still not fully implemented (its tracking issue is #18469), so RFC 401 does not accurately describe any past, present, or future version of Rust. Nevertheless, RFC 401 also would permit coercion of &&&[i32; 5] to &[i32] and by almost the same logic.

The Rustonomicon also has a chapter on coercions and appears to agree with the reference book.

Community
  • 1
  • 1
trentcl
  • 17,886
  • 5
  • 37
  • 60
  • I see. The coercion in "auto-dereferencing rules" is counted as once, whereas the coercions in the code `let arr: &[i32] = &&&[1, 2, 3, 4, 5];` are more than once. The diffrerence between the two occurs because transitive coercions are still unimplemented. Thanks a lot! – Kenta Nakajima Nov 12 '17 at 04:58
  • @KentaNakajima Yep, pretty much. I'm slightly surprised this isn't a better known issue, given how useful coercions are. Although to be honest, I never really thought much about it; I guess that's true for most users. – trentcl Nov 12 '17 at 19:55