Skip to content

Clarification of compiler diagnostic 'no two closures, even if identical, have the same type' #87961

@GregAC

Description

@GregAC

Consider the following function which returns a closure impl

fn returns_closure(hmm: bool) -> impl Fn(i32) -> i32 {
    if hmm {
        |x| x + 1
    } else {
        |x| x * 2
    }
}

fn main() {
    println!("{}", returns_closure(true)(10));
    println!("{}", returns_closure(false)(10));
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5d676e682519cae13533bd1d5c82ebd3

This builds and runs providing the following output:

11
20

This seems reasonable at first glance, however consider a changed version:

fn returns_closure(hmm: bool, y: u32) -> impl Fn(i32) -> i32 {
    if hmm {
        |x| x + y
    } else {
        |x| x * y
    }
}

fn main() {
    println!("{}", returns_closure(true, 3)(10));
    println!("{}", returns_closure(false, 4)(10));
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=43bdb248873d31bc470b0fc9fb8a2ab3

This fails to build with:

error[E0308]: `if` and `else` have incompatible types
 --> src/main.rs:5:9
  |
2 | /     if hmm {
3 | |         |x| x + y
  | |         --------- expected because of this
4 | |     } else {
5 | |         |x| x * y
  | |         ^^^^^^^^^ expected closure, found a different closure
6 | |     }
  | |_____- `if` and `else` have incompatible types
  |
  = note: expected type `[closure@src/main.rs:3:9: 3:18]`
          found closure `[closure@src/main.rs:5:9: 5:18]`
  = note: no two closures, even if identical, have the same type
  = help: consider boxing your closure and/or using it as a trait object

For more information about this error, try `rustc --explain E0308`.

The first example uses two closures and compiles and runs fine, demonstrating we do have two closures of the same type which the output from the second example states cannot happen.

I suspect what's happening is the first closures don't capture from the environment so are seen as function pointers both with type fn(i32) -> i32. So this is just an issue of clarifying the second error message e.g. 'no two closure which capture the environment, even if identical, have the same type'.

Changing the first example as follows seems to confirm this:

fn returns_closure(hmm: bool) -> fn(i32) -> i32 {
    if hmm {
        |x| x + 1
    } else {
        |x| x * 2
    }
}

fn main() {
    println!("{}", returns_closure(true)(10));
    println!("{}", returns_closure(false)(10));
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c628f6a790b886b7975542a7fd89f734

(Builds and runs with identical results to the first example)

From reading the rust reference you could say 'closure' inherently means environment capture (i.e. |x| x + 1 is not a closure), but that conflicts with language used in the rust book so could lead to confusion.

If it's intended that closures from the first example should have different types there's something more serious going on with the type inference/checking.

rustc --version --verbose:

rustc 1.56.0-nightly (ccffcafd5 2021-08-11)
binary: rustc
commit-hash: ccffcafd55e58f769d4b0efc0064bf65e76998e4
commit-date: 2021-08-11
host: x86_64-unknown-linux-gnu
release: 1.56.0-nightly
LLVM version: 12.0.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-diagnosticsArea: Messages for errors, warnings, and lintsT-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