跳至內容

閉包

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