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 {
    panic!()
}

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

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?

3 Likes

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

2 Likes

There's some more directly related conversation here.

[1]


  1. Shush Discord. ↩︎

1 Like