golang defer 三規三返

你熱愛生命嗎?那麼別浪費時間,因爲時間是組成生命的材料。——富蘭克林

1 規則一:延遲函數的參數在 defer 語句出現時就已經確定下來了

示例如下:

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

defer 語句中的 fmt.Println() 參數 i 值在 defer 出現時就已經確定下來,實際上是拷貝了一份。後面對變量 i 的修改不會影響 fmt.Println() 函數的執行,仍然打印”0”。

注意:對於指針類型參數,規則仍然適用,只不過延遲函數的參數是一個地址值,這種情況下,defer 後面的語句對變量的修改可能會影響延遲函數。

2 規則二:延遲函數執行按後進先出順序執行,即先出現的 defer 最後執行

這個規則很好理解,定義 defer 類似於入棧操作,執行 defer 類似於出棧操作。

設計 defer 的初衷是簡化函數返回時資源清理的動作,資源往往有依賴順序,比如先申請 A 資源,再跟據 A 資源申請 B 資源,跟據 B 資源申請 C 資源,即申請順序是: A—>B—>C,釋放時往往又要反向進行。這就是把 deffer 設計成 FIFO 的原因。

每申請到一個用完需要釋放的資源時,立即定義一個 defer 來釋放資源是個很好的習慣。

3  規則三:延遲函數可能操作主函數的具名返回值

3.1 函數返回過程

有一個事實必須要了解,關鍵字 return 不是一個原子操作,實際上 return 只代理彙編指令 ret,即將跳轉程序執行。比如語句 return i,實際上分兩步進行,即將 i 值存入棧中作爲返回值,然後執行跳轉,而 defer 的執行時機正是跳轉前,所以說 defer 執行時還是有機會操作返回值的。

舉個實際的例子進行說明這個過程:

func deferFuncReturn() (result int) {
    i := 1
    defer func() {
       result++
    }()
    return i
}

該函數的 return 語句可以拆分成下面兩行:

result = i
return

而延遲函數的執行正是在 return 之前,即加入 defer 後的執行過程如下:

result = i
result++
return

所以上面函數實際返回 i++ 值。

關於主函數有不同的返回方式,但返回機制就如上機介紹所說,只要把 return 語句拆開都可以很好的理解,下面分別舉例說明

3.1.1 主函數擁有匿名返回值,返回字面值

一個主函數擁有一個匿名的返回值,返回時使用字面值,比如返回”1”、”2”、”Hello” 這樣的值,這種情況下 defer 語句是無法操作返回值的。

一個返回字面值的函數,如下所示:

func foo() int {
    var i int
    defer func() {
        i++
    }()
    return 1
}

上面的 return 語句,直接把 1 寫入棧中作爲返回值,延遲函數無法操作該返回值,所以就無法影響返回值。

3.1.2 主函數擁有匿名返回值,返回變量

一個主函數擁有一個匿名的返回值,返回使用本地或全局變量,這種情況下 defer 語句可以引用到返回值,但不會改變返回值。

一個返回本地變量的函數,如下所示:

func foo() int {
    var i int
    defer func() {
        i++
    }()
    return i
}

上面的函數,返回一個局部變量,同時 defer 函數也會操作這個局部變量。對於匿名返回值來說,可以假定仍然有一個變量存儲返回值,假定返回值變量爲”anony”,上面的返回語句可以拆分成以下過程:

anony = i
i++
return

由於 i 是整型,會將值拷貝給 anony,所以 defer 語句中修改 i 值,對函數返回值不造成影響。

3.1.3 主函數擁有具名返回值

主函聲明語句中帶名字的返回值,會被初始化成一個局部變量,函數內部可以像使用局部變量一樣使用該返回值。如果 defer 語句操作該返回值,可能會改變返回結果。

一個影響函返回值的例子:

func foo() (ret int) {
    defer func() {
        ret++
    }()
    return 0
}

上面的函數拆解出來,如下所示:

ret = 0
ret++
return

這樣打印的值就是 1 了

3.2 小結

defer 我們只需要瞭解它的三規三返就行了,只有牢牢掌握它們,你才能寫的得心應手,有機會我在分享 defer 的原理以及和 recover 的搭配使用。

4 關注公衆號

微信公衆號:堆棧 future

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/dfQra9oiKUTeayw4rq8p5w