サーチ…
前書き
defer
文は、関数呼び出しをリストにプッシュします。保存された呼び出しのリストは、周囲の関数が返った後に実行されます。 Deferは、さまざまなクリーンアップ処理を実行する機能を単純化するために一般的に使用されます。
構文
- defer someFunc(args)
- defer func(){//コードはここに}}()
備考
Deferは、現在実行中の関数の下の呼び出しスタックに新しいスタックフレーム( defer
キーワードの後に呼び出される関数)を挿入defer
によって動作します。これは、スタックが解かれる限り、deferが実行されることを保証します(プログラムがクラッシュしたり、 SIGKILL
取得した場合、遅延は実行されません)。
遅延の基礎
Goでのdefer文は、後で実行されるとマークされた単なる関数呼び出しです。 Defer文は、キーワードdefer
前に付けた通常の関数呼び出しです。
defer someFunction()
defer
文を含む関数が返されると、遅延関数が実行されます。遅延関数への実際の呼び出しは、包含関数が以下の場合に発生します。
- return文を実行する
- 終わりから落ちる
- パニック
例:
func main() {
fmt.Println("First main statement")
defer logExit("main") // position of defer statement here does not matter
fmt.Println("Last main statement")
}
func logExit(name string) {
fmt.Printf("Function %s returned\n", name)
}
出力:
First main statement
Last main statement
Function main returned
ある関数に複数の遅延ステートメントがある場合、それらはスタックを形成します。最後のdefer
は、囲み関数が返った後に実行する最初のdefer
あり、その後に先行defer
の呼び出しが順番に続きます(以下の例では、パニックを引き起こします)。
func main() {
defer logNum(1)
fmt.Println("First main statement")
defer logNum(2)
defer logNum(3)
panic("panic occurred")
fmt.Println("Last main statement") // not printed
defer logNum(3) // not deferred since execution flow never reaches this line
}
func logNum(i int) {
fmt.Printf("Num %d\n", i)
}
出力:
First main statement
Num 3
Num 2
Num 1
panic: panic occurred
goroutine 1 [running]:
....
延期機能は、一度にその引数が評価されていることに注意してくださいdefer
実行されます。
func main() {
i := 1
defer logNum(i) // deferred function call: logNum(1)
fmt.Println("First main statement")
i++
defer logNum(i) // deferred function call: logNum(2)
defer logNum(i*i) // deferred function call: logNum(4)
return // explicit return
}
func logNum(i int) {
fmt.Printf("Num %d\n", i)
}
出力:
First main statement
Num 4
Num 2
Num 1
関数が名前付きの戻り値を持つ場合、その関数内の遅延匿名関数は、関数が返された後でも戻り値にアクセスして更新できます。
func main() {
fmt.Println(plusOne(1)) // 2
return
}
func plusOne(i int) (result int) { // overkill! only for demonstration
defer func() {result += 1}() // anonymous function must be called by adding ()
// i is returned as result, which is updated by deferred function above
// after execution of below return
return i
}
最後に、 defer
ステートメントは一般的によく一緒に行われる操作です。例えば:
- ファイルを開いて閉じる
- 接続と切断
- ミューテックスのロックとロック解除
- waitgroupをdoneとしてマークする(
defer wg.Done()
)
この使用により、実行の流れに関係なく、システムリソースが適切に解放されます。
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close() // Body will always get closed
遅延関数呼び出し
遅延関数呼び出しは、Javaのような言語のfinally
ブロックのようなものに似た目的を果たします。つまり、エラーが発生したかどうかにかかわらず、複数の戻り値を持つ場合にreturn文がヒットしたかどうかにかかわらず、これは、ネットワーク接続やファイルポインタのように閉じなければならないリソースをクリーンアップする場合に便利です。 defer
キーワードは、新しいゴルーチンを開始するgo
キーワードと同様に、遅延関数呼び出しを示します。 go
呼び出しと同様に、関数の引数はすぐに評価されますが、 go
呼び出しとは異なり、遅延関数は同時に実行されません。
func MyFunc() {
conn := GetConnection() // Some kind of connection that must be closed.
defer conn.Close() // Will be executed when MyFunc returns, regardless of how.
// Do some things...
if someCondition {
return // conn.Close() will be called
}
// Do more things
}// Implicit return - conn.Close() will still be called
使用に注意してくださいconn.Close()
の代わりにconn.Close
-あなただけの機能に渡していない、あなたはその引数を含む完全な関数呼び出しを 、延期しています。複数の関数呼び出しを同じ外部関数で遅延させることができ、それぞれが逆の順序で一度だけ実行されます。あなたは閉鎖を延期することもできます - ちょうど括弧を忘れないでください!
defer func(){
// Do some cleanup
}()