Impossible to pass diverging closures as an argument

59d34d9
Opened by Simonas Kazlauskas at 2020-05-04 00:17:28
fn banana<F: FnOnce()>(f: F) -> ! { f(); loop {} }

fn main() {
    banana(move || -> ! { loop {} })
}

result

error[E0271]: type mismatch resolving `<[closure@<anon>:6:12: 6:36] as std::ops::FnOnce<()>>::Output == ()`
 --> <anon>:6:5
  |
6 |     banana(move || -> ! { loop {} })
  |     ^^^^^^ expected !, found ()
  |
  = note: expected type `!`
  = note:    found type `()`
  = note: required by `banana`

Used to work in 1.8.0, so is a regression from stable to stable.

  1. @eddyb says its a bugfix; worked around with

    fn banana<R, F: FnOnce() -> R>(f: F) -> R { f() }
    

    Simonas Kazlauskas at 2017-01-13 13:32:08

  2. I am not 100% sure though. cc @canndrew @nikomatsakis

    Eduard-Mihai Burtescu at 2017-01-13 14:20:19

  3. Reopening for discussion.

    Simonas Kazlauskas at 2017-01-13 16:01:12

  4. Well, I suppose that would not be expected to work, if the true return type of the closure here is ! (as opposed to saying that the return type of the closure is a free variable that can be any type, including !). The latter might make more sense though?

    Niko Matsakis at 2017-01-13 19:19:56

  5. But, you declared the closure type F to return (), did you not? This code typechecks:

    #![feature(never_type)]
    
    fn banana<F: FnOnce() -> !>(f: F) -> ! { f(); loop {} }
    
    fn main() {
        banana(move || -> ! { loop {} })
    }
    

    Alex Burka at 2017-01-13 20:18:58

  6. @durka the original code used to work on stable rustc. Presence of a feature in your “typechecks” example prevents that. I already pointed out what was a sufficient workaround for me in the 2nd comment.


    Something like

    fn banana(f: fn()) -> ! { f(); loop {} }
    fn diverging() -> ! { loop {} }
    fn main() {
        banana(diverging)
    }
    

    does not work either, which is consistent with the “bug fix” thinking, but it would be nice if there was a way to somehow encode code like in @durka’s comment above (i.e. banana is explicitly diverging function, what F is bounded on does not matter) in stable rust (e.g. by stabilising !?).

    That being said, I’m somewhat surprised by this behaviour. I would have expected that -> ! would allow passing a diverging function to any argument of type fn(...) -> *, as ! is supposed to coerce to anything, function will never return etc.

    Simonas Kazlauskas at 2017-01-14 05:44:31

  7. I would have expected that -> ! would allow passing a diverging function to any argument of type fn(...) -> *, as ! is supposed to coerce to anything, function will never return etc.

    In order to support that sort of behavior we'd need to make ! a subtype of everything. An expression of type ! can coerce to an expression of type T but that doesn't mean you can use a type which implements Fn() -> ! in place of a type which implements Fn() -> ().

    I don't know why that code ever worked on stable rust but, from memory, diverging closures were never well-behaved until the feature(never_type) changes.

    Andrew Cann at 2017-01-14 06:59:41

  8. Today:

    error[E0271]: type mismatch resolving `<[closure@src\main.rs:4:12: 4:36] as std::ops::FnOnce<()>>::Output == ()`                                                                                            
     --> src\main.rs:4:5
      |
    1 | fn banana<F: FnOnce()>(f: F) -> ! { f(); loop {} }
      |              -------- required by this bound in `banana`
    ...
    4 |     banana(move || -> ! { loop {} })
      |     ^^^^^^ expected `()`, found `!`
      |
      = note: expected unit type `()`
                      found type `!`
    

    Donough Liu at 2020-05-04 00:17:28