[Golang] Defer, Panic 和 Recovery
defer
Defer, Panic, and Recover @ Golang Blog
defer
這個 statement 可以用來在函式最終要回傳前被執行,類似 clean-up 的動作。有三個最主要的原則:
defer 中的值在執行時就寫入
這個 defer
中所使用到的參數是在執行到的時候就已經被帶入:
func main() {
language := "Go"
defer fmt.Print(language + "\n")
language = "Node.js"
fmt.Print("Hello ")
}
// 輸出:Hello Go
defer 是後進先出(last-in-first-out)
當有多個 defer
時,採用的是 後進先出(last-in-first-out) 原則:
func main() {
language := "Go"
defer fmt.Print(" to " + language + "\n")
language = "Node.js"
defer fmt.Print("from " + language)
fmt.Print("Hello ")
}
// 輸出:Hello from Node.js to Go
defer 可以用在函式回傳具名的變數
當函式回傳的值是具名的變數時,defer 可以去修改最終回傳出去的值:
// result 2
func c() (i int) {
defer func() { i++ }()
return 1
}
Panic
panic 會停止原本的 control flow,並進入 panicking。當函式 F 呼叫 panic 時,F 會停止執行,並且執行 F 中的 deferred function,最終 F 會回到呼叫它的函式(caller)。回到 Caller 之後,F 也會進入 panic,直到當前的 goroutine 都 returned,並導致程式 crash。
Recover
recover 則是可以讓 panicking 的 goroutine 重新取得控制權,它只有在 deferred function 中執行是有用的。在一般函式運行的過程中,呼叫 recover 會回傳 nil
並且沒有任何效果,一旦當前的 goroutine panicking 時,recover 會攔截 panic 中給的 value,並讓函式回到正常的執行。
範例程式碼
下面是一個整合 defer
, panic
和 recover
的程式碼。可以從下面的程式碼中看到:
- 呼叫
panic
之後會進入該函式的defer
,並開始往外層 stack 傳遞 defer
會是 last-in-first-outrecover
要放在defer
中,從r := recover()
可以取得panic()
參數中帶的值- recover 之後,該 panic 就不會在繼續往外層 stack 傳遞,因此外層函式可以繼續正常運作
// 程式來源:https://blog.golang.org/defer-panic-and-recover
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func() {
// 可以取得 panic 的回傳值
r := recover()
if r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
}
func g(i int) {
if i > 3 {
panicNumber := fmt.Sprintf("%v", i)
fmt.Println("Panicking!", panicNUmber)
// log.Fatalln(panicNumber) // 如果使用 fatal 程式會直接終止,defer 也不會執行到
panic(panicNumber)
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}