Type inference and implicit coercions puzzle

As a fan of figuring out mental models of what the compiler does around implicit features (like borrow checking, type inference, etc…), I had just written something about how I think, roughtly, implicit coercions and type inference interact, ==> HERE.

However… While testing some code examples for that, I’ve come across behavior I have trouble making any sense of. Of course I could try digging up the relevant place in rustc’s implementation, but where’s the fun in that? (Also, that might not be that trivial to find.)

I have minimized the weird behavior to the following example. Uncomment different lines at the beginning to compare:

type Wrap<T> = T; // works
// use core::cell::Cell as Wrap; // works
// type Wrap<T> = (T, (), T); // works

// type Wrap<T> = (T, T, ()); // doesn't work
// use Option as Wrap; // doesn't work
// use Box as Wrap; // doesn't work

struct Struct<T>(Wrap<T>);

fn make<T>() -> T {

fn to_box<T>(_arg: Struct<T>) -> Box<T> {

pub fn test() {
    let _var: Box<[u8]> = to_box(make::<Struct<[u8; 1]>>());

Rust Playground

With any of the “doesn’t work” alternatives, the error is

error[E0308]: mismatched types
  --> src/lib.rs:20:34
20 |     let _var: Box<[u8]> = to_box(make::<Struct<[u8; 1]>>());
   |                           ------ ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Struct<[u8]>`, found `Struct<[u8; 1]>`
   |                           |
   |                           arguments to this function are incorrect
   = note: expected struct `Struct<[u8]>`
              found struct `Struct<[u8; 1]>`
note: function defined here
  --> src/lib.rs:15:4
15 | fn to_box<T>(_arg: Struct<T>) -> Box<T> {
   |    ^^^^^^    ---------------

error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
  --> src/lib.rs:20:34
20 |     let _var: Box<[u8]> = to_box(make::<Struct<[u8; 1]>>());
   |                           ------ ^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |                           |
   |                           required by a bound introduced by this call
   = help: the trait `Sized` is not implemented for `[u8]`
note: required by a bound in `to_box`
  --> src/lib.rs:15:11
15 | fn to_box<T>(_arg: Struct<T>) -> Box<T> {
   |           ^ required by this bound in `to_box`
help: consider relaxing the implicit `Sized` restriction
15 | fn to_box<T: ?Sized>(_arg: Struct<T>) -> Box<T> {
   |            ++++++++

Any good idea as to what the “rule” here could be? And then also why that rule? Or maybe it should just qualify as “compiler bug” anyways?


Seems like the ones that work[1] have T as the last field. That's not the only thing you need to be considered for unsized coercion, but it is a requirement.

  1. I didn't think about Option really ↩︎

1 Like

So, speculating, but I think it's deciding it should unsize the Struct<[u8; 1]> if the last field is the [u8; 1], without checking the bounds on the functions or types, and without verifying the other unsizing requirements (no other apperance of the type).

Like pehaps,

  • Is the argument definitely Sized?
    • Yes, because the last field is definitely Sized
      • Can't coerce it, can't be a to_box::<[u8]> call then
    • Not definitely, because the last field is [u8; 1]
      • OK they probably meant to_box::<[u8]> then

From here:

my understanding is that to prove Unsized: Sized, only the last field of Unsized has to be considered, and this may be used sometimes before Unsized's definition was checked


There's some more directly related conversation here.


  1. Shush Discord. ↩︎

1 Like