Skip to content

Inconsistent fn casting behavior in if-else branches for tuple and struct #135970

Open
@gaesa

Description

@gaesa

I tried this code:

Version 1 (Compiles successfully):

use std::hint::black_box;
use std::any::type_name_of_val;

fn foo() {}
fn bar() {}

fn main() {
    let x = if black_box(true) { foo } else { bar };
    println!("{}", type_name_of_val(&x)); // observing `fn` items being cast to `fn` pointers, output: `fn()`
}

Version 2 (Fails to compile, tuple)

use std::hint::black_box;
use std::any::type_name_of_val;

fn foo() {}
fn bar() {}

fn main() {
    let x = if black_box(true) { (foo,) } else { (bar,) }; // mismatched types, no casting observed
    println!("{}", type_name_of_val(&x.0));
}

Version 3 (Fails to compile, struct)

use std::any::type_name_of_val;
use std::hint::black_box;

fn foo() {}
fn bar() {}

struct F<T>
where
    T: Fn(),
{
    inner: T,
}

fn main() {
    let x = if black_box(true) {
        F { inner: foo }
    } else {
        F { inner: bar }
    }; // mismatched types, no casting observed
    println!("{}", type_name_of_val(&x.inner));
}

I expected to see this happen:
In all versions, I expected the code to either compile successfully or fail with the same type mismatch error.

Instead, this happened:
Version 1 compiles successfully, while version 2 and version 3 fail with the same error expected fn item, found a different fn item. Explicitly specifying the fn pointer type for x resolves the issue. By the way, the use of impl_trait_in_bindings feature does not affect this behavior.

Meta

rustc --version --verbose:

rustc 1.86.0-nightly (99768c80a 2025-01-23)
binary: rustc
commit-hash: 99768c80a1c094a5cfc3b25a04e7a99de7210eae
commit-date: 2025-01-23
host: x86_64-unknown-linux-gnu
release: 1.86.0-nightly
LLVM version: 19.1.7

RUST_BACKTRACE=1 doesn't provide additional information for any of the versions.

Version 1 only provides Finished ... and no other information.

Version 2

   Compiling rust-test v0.1.0 (.../rust-test)
error[E0308]: `if` and `else` have incompatible types
 --> src/main.rs:8:50
  |
8 |     let x = if black_box(true) { (foo,) } else { (bar,) }; // mismatched types, no casting observed
  |                                  ------          ^^^^^^ expected `(fn() {foo},)`, found `(fn() {bar},)`
  |                                  |
  |                                  expected because of this
  |
  = note: expected tuple `(fn() {foo},)`
             found tuple `(fn() {bar},)`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `rust-test` (bin "rust-test") due to 1 previous error

Version 3

   Compiling rust-test v0.1.0 (.../rust-test)
error[E0308]: `if` and `else` have incompatible types
  --> src/main.rs:18:9
   |
15 |       let x = if black_box(true) {
   |  _____________-
16 | |         F { inner: foo }
   | |         ---------------- expected because of this
17 | |     } else {
18 | |         F { inner: bar }
   | |         ^^^^^^^^^^^^^^^^ expected `F<fn() {foo}>`, found `F<fn() {bar}>`
19 | |     }; // mismatched types, no casting observed
   | |_____- `if` and `else` have incompatible types
   |
   = note: expected struct `F<fn() {foo}>`
              found struct `F<fn() {bar}>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `rust-test` (bin "rust-test") due to 1 previous error

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-coercionsArea: implicit and explicit `expr as Type` coercionsA-type-systemArea: Type systemC-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions