为什么相同签名的 Rust 闭包不具有相同的类型?

Tes*_*est 0 closures rust

根据这个答案,Rust 将具有相同返回类型的异步块和闭包视为不同类型。

为什么 Rust 将具有相同签名的闭包视为不同类型?这是理论上的限制还是编译器的限制?

在 Haskell 中,它们受到相同的对待。

Cha*_*man 7

AFAIK,Haskell 将它们分配在堆上。这相当于Box<dyn Fn()>Rust 中的。由于您将它们装箱,因此您知道大小将始终为2*usize(数据指针和虚函数表指针)。

默认情况下,Rust 不会对闭包进行装箱。相反,每个闭包都会获得一个新的、不可命名的结构,其中包含所有捕获的变量。例如,以下内容:

let a: i32 = 123;
let closure = |b: i32| -> i32 { a + b };
Run Code Online (Sandbox Code Playgroud)

翻译成类似的东西(不完全是,但并不重要):

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
    }
}
Run Code Online (Sandbox Code Playgroud)

这样效率更高,本质上使得闭包零成本,但我们要付出的代价是不同的闭包在内存中的布局不同。因此,即使它们具有相同的签名,它们也不相等,因此您不能返回其中之一。

此外,即使它们碰巧具有相同的布局,您仍然无法返回不同的闭包类型,因为闭包是静态分派的。我们不依赖 vtable 来查找闭包代码,而是直接调用它。同样,这使它们的成本为零,这对 Rust 很重要,但意味着如果它们不是同一个闭包,您将无法知道要调用哪些代码。

有一种特殊情况:不捕获任何内容的闭包可以转换为函数指针。这就像一个没有数据指针的虚函数表,因为我们知道它们没有数据:

let closure = if condition {
    || {} as fn()
} else {
    || {} as fn()
};
Run Code Online (Sandbox Code Playgroud)