Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

closures

rust

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`


like image 981
jm4ier Avatar asked Apr 10 '21 12:04

jm4ier


People also ask

How do closures work in Rust?

Rust's closures are anonymous functions you can save in a variable or pass as arguments to other functions. You can create the closure in one place and then call the closure elsewhere to evaluate it in a different context. Unlike functions, closures can capture values from the scope in which they're defined.

Why Rust closures are somewhat hard?

Rust closures are harder for three main reasons: The first is that it is both statically and strongly typed, so we'll need to explicitly annotate these function types. Second, Lua functions are dynamically allocated ('boxed'.)

Are closures copy rust?

A closure is Clone or Copy if it does not capture any values by unique immutable or mutable reference, and if all values it captures by copy or move are Clone or Copy , respectively.

What is closure type?

Closure types always appear in comments, never in the syntax of JavaScript itself. You can specify the data type of any variable, property, expression or function parameter with a type expression. Use a type expression with the @param tag to declare the type of a function parameter.


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.

like image 131
Emoun Avatar answered Oct 10 '22 21:10

Emoun