閉包¶
捕獲的區塊和 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