Type inference impeded by tuple

Please refer to this example:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=860b94ea52afff7463ff175413e4856f

in which as far as I understand it:

  • test_ok_no_tuple() binds out directly,
  • test_ok_with_hint() binds out by destructuring a
    tuple but with a type hint supplied,
  • test_bad_no_hint() fails to bind out by destructuring
    on account of the type hint being omitted (“expected fn item,
    found a different fn item”).

What is the reason for rustc’s inability to infer the equivalence
of the signatures of f_a() and f_b() in test_bad_no_hint()?

This is happening because each function in rust has its own zero-sized function item type. The unadorned code sees that you're using one fn item type and says "Okay, they wanted a tuple of (fn(std::string::String) {f_a}, bool), got it." then complains when you later try to create a tuple of (fn(std::string::String) {f_b}, bool) in the same binding.

But when you specify your type hint you're triggering a coersion from fn item type -> fn pointer type, which means the tuple is now of type (fn(std::string::String), bool) and compilation succeeds.

There's one last bit to the magic to explain why the non-tuple case works. Let's use my favorite let () = technique to show some types:

let out_1 = match e {
    E::Foo => f_a,
    E::Bar => f_a,
};
let () = out_1;

let out_2 = match e {
    E::Foo => f_a,
    E::Bar => f_b,
};
let () = out_2;
error[E0308]: mismatched types
  --> src/main.rs:11:9
   |
11 |     let () = out_1;
   |         ^^ expected fn item, found ()
   |
   = note: expected type `fn(std::string::String) {f_a}`
              found type `()`

error[E0308]: mismatched types
  --> src/main.rs:17:9
   |
17 |     let () = out_2;
   |         ^^ expected fn pointer, found ()
   |
   = note: expected type `fn(std::string::String)`
              found type `()`

As you can see, in the first case where the branches all produce the same fn, the compiler infers the type to be that specific fn (a zero-sized type). In the case where they are different fns, the compiler infers the type to be a function pointer (a pointer-sized type).

The reason is because the compiler infers the output of a match statement to be a common type that all branches can be coerced into. This is what allows e.g. one branch of a match to produce a &str while another branch produces a &String.

Thus, without the tuple, your example works because fn_a can be coerced to fn(String). With the tuple, it fails, because (fn_a, bool) cannot be coerced into (fn(String), bool). (because, generally speaking, coercions do not apply to types nested within another type)

1 Like

Thus, without the tuple, your example works because fn_a can
be coerced to fn(String). With the tuple, it fails, because
(fn_a, bool) cannot be coerced into (fn(String), bool).
(because, generally speaking, coercions do not apply to types
nested within another type)

That last bit in parentheses is what I was missing. Thanks a lot
for the clarification!

1 Like