Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
GregAC opened this issue Aug 12, 2021 · 5 comments
Labels
A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@GregAC
Copy link

GregAC commented Aug 12, 2021

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
@GregAC GregAC added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Aug 12, 2021
@thomcc
Copy link
Member

thomcc commented Aug 12, 2021

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'.

I think your analysis here is mostly right. The thing is it's actually true that no two closures have the same type, even if they don't capture their environment. What's happening is that closure-to-fn coersion is taking place in the first example — they do have different types, but are coerced to a common type.

So, I'm not sure how to fix the diagnostic, since it's not wrong, it just looks wrong whereas your suggestion is more wrong, even though it appears to match the behavior... The behavior is just unintuitive...

(Sadly, I don't think the rules by which this happens are very well specified — I believe it's less specified than Deref or Unsizing coersion, but maybe I'm just unaware of were the docs are).

@GregAC
Copy link
Author

GregAC commented Aug 13, 2021

I guess it depends how verbose you allow the diagnostics to become an extra

note: two closures can be coerced to the same fn type under certain circumstances, this was not possible here

or similar could work?

@jyn514
Copy link
Member

jyn514 commented Aug 16, 2021

I think it would be useful to suggest coercing both closures to a function pointer if possible, but I don't think the diagnostic needs to mention function pointers if coercing isn't possible. Diagnostics should only give you as much info as you need to fix the problem.

@camsteffen
Copy link
Contributor

I recently learned this is possible and I also wish rustc had taught me this sooner. I think it should say something like:

help: consider using inputs instead of captured variables

 |        |x, y| x + y
            +++

@hardfau1t
Copy link

Similar to above

fn main() {
    let haa = if true {
        Some(|x:u32| x + 1)
    } else {
        Some(|x:u32|  x + 2)
    };
}

this throws error as if and else have incompatible types.
But this compiles fine

fn main() {
    let haa = if true { 
        |x: u32| x + 1 
    } else {
        |x: u32| x + 2 
    };
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

5 participants