Rust: Why does the binding of the closure to a variable change the type?

Viewed 280

I have this (most simplified) piece of code:

fn returns_closure() -> Box<dyn Fn(&u64)> {
    let closure = |_| ();
    Box::new(closure)
}

This does not compile with a rather unhelpful error message:

error[E0308]: mismatched types
 --> src/main.rs:3:5
  |
3 |     Box::new(closure)
  |     ^^^^^^^^^^^^^^^^^ one type is more general than the other
  |
  = note: expected type `FnOnce<(&u64,)>`
             found type `FnOnce<(&u64,)>`

However, when I don't bind the closure to a variable first, and create it directly in the constructor of the Box, it does compile:

fn returns_closure() -> Box<dyn Fn(&u64)> {
    Box::new(|_| ())
}

Why does the first one fail to compile, and what is the difference between the two?


Edit: Emouns answer seems to be correct. I compiled the exact same code with the nightly toolchain (1.52.0), and got a better error:

error: implementation of `FnOnce` is not general enough
 --> src/main.rs:3:5
  |
3 |     Box::new(closure)
  |     ^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
  |
  = note: closure with signature `fn(&'2 u64)` must implement `FnOnce<(&'1 u64,)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 u64,)>`, for some specific lifetime `'2`


1 Answers

As @frankenapps pointed out, if you use let closure = |_: &u64|(); then you don't get this error. So what is going on?

To try to figure this out I made the following edits that use explicit lifetimes (instead of having the compiler elide them for us) in an effort to get the same error message:

fn returns_closure<'a>() -> Box<dyn Fn(&'_ u64)> {
    let closure = |_: &'a u64| ();
    Box::new(closure)
}

Running this code gives us the following error message:

rror[E0308]: mismatched types
 --> src/lib.rs:4:5
  |
4 |     Box::new(closure)
  |     ^^^^^^^^^^^^^^^^^ one type is more general than the other
  |
  = note: expected type `for<'r> Fn<(&'r u64,)>`
             found type `Fn<(&'a u64,)>`
note: this closure does not fulfill the lifetime requirements
 --> src/lib.rs:3:19
  |
3 |     let closure = |_: &'a u64| ();
  |                   ^^^^^^^^^^^^^^^

error[E0308]: mismatched types
 --> src/lib.rs:4:5
  |
4 |     Box::new(closure)
  |     ^^^^^^^^^^^^^^^^^ one type is more general than the other
  |
  = note: expected type `FnOnce<(&u64,)>`
             found type `FnOnce<(&'a u64,)>`
note: this closure does not fulfill the lifetime requirements
 --> src/lib.rs:3:19
  |
3 |     let closure = |_: &'a u64| ();
  |                   ^^^^^^^^^^^^^^^

The error message is not the exact same, since we are using explicit lifetimes, but the type of error is still one type is more general than the other.

So what we see here might be an error in lifetime elision. You can see that I have given the closure the same lifetime as the function ('a), however the function returns dyn Fn(&'_ u64). Here notice the '_ which is a stand-in for any lifetime, not just 'a. Therefore, the return type &'_ u64is more general (accepts more lifetimes) than the closure (&'a u64) and we get the error.

I tried other lifetime combinations, but this one was the only one to give this type of error. But whether this is actually what is going on I cannot say.

Related