Why don't Rust closures of the same signature have the same type?

Per this answer, Rust treats async blocks and closures with the same return type as different types.

Why does Rust treat closures with the same signature as different types? Is this a theoretical limitation or a limitation of the compiler?

In Haskell they are treated the same.

>Solution :

AFAIK, Haskell allocates them on the heap. This is equivalent to Box<dyn Fn()> in Rust. Since you box them, you know the size will always be 2*usize (a data pointer and a vtable pointer).

By default Rust does not box closures. Rather, each closure gets a new, unnameable struct with all captured variables. For example, the following:

let a: i32 = 123;
let closure = |b: i32| -> i32 { a + b };

Translates into something like (not exactly but it doesn’t matter):

struct Closure { a: i32 }
impl FnOnce<(i32,)> for Closure {
    type Output = i32;
    extern "rust-call" fn call_once(self, (b,): (i32,)) -> i32 {
        self.a + b
    }
}

This is more efficient, and essentially makes the closure zero cost, but the price we have to pay is that different closures have different layouts in memory. Because of that they are not equal even if they have the same signature, so you cannot return one of them.

Moreover, even if they happen to have the same layout you still cannot return different closure types, because closures are dispatched statically. Instead of relying on a vtable to find the closure code we just call it directly. Again, this makes them zero cost which is important for Rust but means you cannot know what code to call if they are not the same closure.

There is one special case: closures that capture nothing can be converted to a function pointer. This is like just a vtable without the data pointer, because we know they don’t have data:

let closure = if condition {
    || {} as fn()
} else {
    || {} as fn()
};

Leave a Reply