跳至内容

闭包

捕获的块和 Proc 字面量闭包局部变量和 self。这可以通过一个示例更好地理解

x = 0
proc = ->{ x += 1; x }
proc.call # => 1
proc.call # => 2
x         # => 2

或者使用从方法返回的 Proc

def counter
  x = 0
  ->{ x += 1; x }
end

proc = counter
proc.call # => 1
proc.call # => 2

在上面的示例中,即使 x 是一个局部变量,它也被 Proc 字面量捕获了。在这种情况下,编译器会在堆上分配 x 并将其用作 Proc 的上下文数据以使其工作,因为通常局部变量存在于堆栈中,并在方法返回后消失。

闭包变量的类型

编译器通常对局部变量的类型有相当的智能。例如

def foo(&)
  yield
end

x = 1
foo do
  x = "hello"
end
x # : Int32 | String

编译器知道,在块执行完后,x 可以是 Int32 或 String(它可能知道它将始终是 String,因为方法总是 yield;这在将来可能会得到改进)。

如果在块之后 x 被分配了其他东西,编译器就知道类型已经改变了

x = 1
foo do
  x = "hello"
end
x # : Int32 | String

x = 'a'
x # : Char

但是,如果 x 被 Proc 闭包,则类型始终是所有对它赋值的混合类型

def capture(&block)
  block
end

x = 1
capture { x = "hello" }

x = 'a'
x # : Int32 | String | Char

这是因为捕获的块可能已被存储在类或实例变量中,并在指令之间在单独的线程中被调用。编译器不会对此进行详尽的分析:它只是假设,如果一个变量被 Proc 捕获,则该 Proc 调用的时间是未知的。

这也发生在普通的 Proc 字面量中,即使很明显 Proc 没有被调用或存储

x = 1
->{ x = "hello" }

x = 'a'
x # : Int32 | String | Char