Tuples of functions do not coerce to tuples of function pointers

45f70f8
Opened by Denis Golovan at 2024-12-21 05:09:59

Hi

Following code does not compile in current nightly even though error message does not seem to have valid meaning. Returning single function in both match arms compiles successfully.

fn m(i:i8) {
  let (l, r) = match i {
     0 => {
        fn f1()-> bool { true };
        fn f2()-> bool { true };
        (f1, f2)
     }
     1 => {
        fn f1()-> bool { true };
        fn f2()-> bool { true };
        (f1, f2)
     }
  };
}

fn main() {
  m(0)
}

error[E0308]: match arms have incompatible types
  --> src/main.rs:2:16
   |
2  |     let (l, r) = match i {
   |  ________________^
3  | |      0 => {
4  | |         fn f1()-> bool { true };
5  | |         fn f2()-> bool { true };
...  |
12 | |      }
13 | |   };
   | |___^ expected fn item, found a different fn item
   |
   = note: expected type `(fn() -> bool {m::f1}, fn() -> bool {m::f2})` (fn item)
              found type `(fn() -> bool {m::f1}, fn() -> bool {m::f2})` (fn item)
note: match arm with an incompatible type
  1. Each function has a unique type. You just happen to have given them the same names so the error message sounds silly. This sounds annoying and pointless but it isn't: the fact that each one is unique means there is only one possible value (namely, a pointer to the unique function), so the value actually isn't stored at all: f1 and (f1, f2) have size zero. When you cast or coerce to a function pointer then they have to store the value and are no longer zero-sized.

    So you can put in explicit casts and it will work: (f1 as fn()->bool, f2 as fn()->bool). As for why it works when there is only one function, I believe automatic coercions are attempted to make the match arms compatible, and function "pointers" coerce but tuples don't.

    Alex Burka at 2017-08-04 18:25:07

  2. Ok, thanks for explanation. Casting works fine. Although, automatic coercion is better, imho.

    Denis Golovan at 2017-08-07 08:11:49

  3. I ran into this, here's a simplified example:

    fn f() {}
    fn g() {}
    
    fn main() {
        [f, g];
    }
    

    The above compiles, but apply this:

    -    [f, g];
    +    [(f,), (g,)];
    

    And you'll get this error:

    error[E0308]: mismatched types
     --> src/main.rs:5:13
      |
    5 |     [(f,), (g,)];
      |             ^ expected fn item, found a different fn item
      |
      = note: expected fn item `fn() {f}`
                 found fn item `fn() {g}`
      = note: different fn items have unique types, even if their signatures are the same
      = help: consider casting both fn items to fn pointers using `as fn()`
    

    I found two workarounds:

    1. Cast each function with as fn() (use a type alias if your type is huge).
    type MyFn = fn();
    [(f as MyFn,), (g as MyFn,)];
    
    1. Add type annotations.
    type MyTuple = (fn(),);
    let _: [MyTuple; 2] = [(f,), (g,)];
    

    I personally prefer annotations over casting repeatedly.

    João Marcos at 2024-01-30 02:00:16