Can only pass closure to `sort_by`if it not bound to a variable

I want to sort a list of numbers using something along these lines (smallest reproducible example):

fn foo(bar: i32) -> i32 {
    bar
}

fn main() {
    let comparator = |&a, &b| foo(a).cmp(&foo(b));
    let mut baz = vec![5, 2, 3, 8, 0, -6];
    baz.sort_by(comparator);
}

However, it fails to compile, and I get this message:

error[E0308]: mismatched types
   --> src/main.rs:36:5
    |
36  |     baz.sort_by(comparator);
    |     ^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
    |
    = note: expected trait `for<'a, 'b> FnMut<(&'a i32, &'b i32)>`
               found trait `FnMut<(&i32, &i32)>`
note: this closure does not fulfill the lifetime requirements
   --> src/main.rs:34:22
    |
34  |     let comparator = |&a, &b| foo(a).cmp(&foo(b));
    |                      ^^^^^^^^
note: the lifetime requirement is introduced here
   --> ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/slice.rs:260:12
    |
260 |         F: FnMut(&T, &T) -> Ordering,
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:36:5
   |
36 |     baz.sort_by(comparator);
   |     ^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&'2 i32, &i32) -> std::cmp::Ordering` must implement `FnOnce<(&'1 i32, &i32)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&'2 i32, &i32)>`, for some specific lifetime `'2`

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:36:5
   |
36 |     baz.sort_by(comparator);
   |     ^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&i32, &'2 i32) -> std::cmp::Ordering` must implement `FnOnce<(&i32, &'1 i32)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&i32, &'2 i32)>`, for some specific lifetime `'2`

I don't get the error messages, what is the difference between for<'a, 'b> FnMut<(&'a i32, &'b i32)> and FnMut<(&i32, &i32)> ?
The error message also mentions the FnOnce trait, and I don't understand why, since the closure should implement FnMut (and sort_by requires FnMut) (or maybe FnOnce is implemented if FnMut is implemented ?

But the biggest thing that I don't understand is that, if I don't assign a name to the closure and instead pass it like this: baz.sort_by(|&a, &b| foo(a).cmp(&foo(b)));, it compiles !?

What is going on, and how can I pass my closure ?

The explicit typing of closures can be tricky due to various reasons (e.g. the compiler tries to "make them work", but sometimes this can actually make the code not compile).

Annotating this particular closure with a concrete fn pointer type helps:

let comparator: fn(&i32, &i32) -> _ = |&a, &b| foo(a).cmp(&foo(b));
1 Like

Adding type signatures involving an explicitly mentioned reference type tends to make the closure infer a generic-over-that-reference’s-lifetime kind of type, so in this case this code actually compiles due to the additional “: &_” type annotations (you could of course also spell out the whole : &i32 instead).

fn foo(bar: i32) -> i32 {
    bar
}

fn main() {
    let comparator = |&a: &_, &b: &_| foo(a).cmp(&foo(b));
    let mut baz = vec![5, 2, 3, 8, 0, -6];
    baz.sort_by(comparator);
}

Passing a closure to a generic function that expects an argument with a closure-trait bound has some special compiler support for interacting with the type inference of the closure itself. Assigning to a variable first seems to remove the closure sufficiently far from its usage so that this compiler support for aiding closure type inference can no longer work. A closure without any such aid by e.g. a Fn… trait signature will infer the type based on the closure body alone, but also before type inference even begins (probably because doing anything smarter is actually quite hard from a compiler implementation stand point) – but after the type signature on the closure, if present, has been taken into consideration, that any closure argument with unknown type will have some single concrete unknown to-be-inferred type, so it’s simply (and in this case essentially incorrectly) assumed not to be a generic family of types potentially with generic lifetime parameter(s)).

Thus due to this assumption that type inference makes, the compiler allows the closure let comparator = |&a, &b| foo(a).cmp(&foo(b)); to be a (&'foo i32, &'bar i32) -> Ordering closure for some concrete choice of lifetimes 'foo and 'bar, but it will no longer be permitted to be a closure generic over those lifetime arguments.

3 Likes

So it's a kind of type inference that doesn't work well for closures, and I need to manually type the variables ?

Unfortunately however, I think I simplified my issue too much. In reality, foo is a method, and therefore captures a variable (that can't be moved inside the closure).

Here is a more realistic version:

struct Foo;
impl Foo {
    fn nop(&self, bar: i32) -> i32 {
        bar
    }
}

fn main() {
    let foo = Foo;
    let comparator: fn(&i32, &i32) -> _ = |&a, &b| foo.nop(a).cmp(&foo.nop(b));
    let mut baz = vec![5, 2, 3, 8, 0, -6];
    baz.sort_by(comparator);
}

Trying to annotate the closure returns an error message saying that "closures can only be coerced to fn types if they do not capture any variables"

It looks like annotating only the arguments also works: Playground

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.