跳至主要内容

[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, panicrecover 的程式碼。可以從下面的程式碼中看到:

  • 呼叫 panic 之後會進入該函式的 defer,並開始往外層 stack 傳遞
  • defer 會是 last-in-first-out
  • recover 要放在 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)
}