Why does boxing an array of function pointers with `box` syntax only work with a temporary `let` binding?

This looks like an idiosyncracy of the type inference algorithm to me, and there probably is no deeper reason for this except that the current inference algorithm happens to behave like it does. There is no formal specification of when type inference works and when it doesn't. If you encounter a situation that the type inference engine cannot handle, you need to add type annotations, or rewrite the code in a way that the compiler can infer the types correctly, and that is exactly what you need to do here.

Each function in Rust has its own individual function item type, which cannot be directly named by syntax, but is diplayed as e.g. fn() -> u32 {foo} in error messages. There is a special coercion that converts function item types with identical signatures to the corresponding function pointer type if they occur in different arms of a match, in different branches of an if or in different elements of an array. This coercion is different than other coercions, since it does not only occur in explicitly typed context ("coercion sites"), and this special treatment is the likely cause for this idiosyncracy.

The special coercion is triggered by the binding

let tmp = [foo, bar];

so the type of tmp is completely determined as [fn() -> u32; 2]. However, it appears the special coercion is not triggered early enough in the type inference algorithm when writing

let b = box [foo, bar] as Box<[_]>;

The compiler first assumes the item type of an array is the type of its first element, and apparently when trying to determine what _ denotes here, the compiler still hasn't updated this notion – according to the error message, _ is inferred to mean fn() -> u32 {foo} here. Interestingly, the compiler has already correctly inferred the full type of box [foo, bar] when printing the error message, so the behaviour is indeed rather weird. A full explanation can only be given when looking at the compiler sources in detail.

Rust's type solver engine often can't handle situations it should theoretically be able to solve. Niko Matsakis' chalk engine is meant to provide a general solution for all these cases at some point in the future, but I don't know what the status and the timeline of that project is.


[T; N] to [T] is an unsizing coercion.

CoerceUnsized<Pointer<U>> for Pointer<T> where T: Unsize<U> is implemented for all pointer types (including smart pointers like Box and Rc). Unsize is only implemented automatically, and enables the following transformations:

  • [T; n] => [T]

These coercions only happen at certain coercion sites:

Coercions occur at a coercion site. Any location that is explicitly typed will cause a coercion to its type. If inference is necessary, the coercion will not be performed. Exhaustively, the coercion sites for an expression e to type U are:

  • let statements, statics, and consts: let x: U = e
  • Arguments to functions: takes_a_U(e)
  • Any expression that will be returned: fn foo() -> U { e }
  • Struct literals: Foo { some_u: e }
  • Array literals: let x: [U; 10] = [e, ..]
  • Tuple literals: let x: (U, ..) = (e, ..)
  • The last expression in a block: let x: U = { ..; e }

Your case B is a let statement, your case C is a function argument. Your case A is not covered.


Going on pure instinct, I'd point out that box is an unstable magic keyword, so it's possible that it's just half-implemented. Maybe it should have coercions applied to the argument but no one has needed it and thus it was never supported.

Tags:

Rust