闭包¶
捕获的块和 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