[インデックス 10703] ファイルの概要
このコミットは、Go言語のドキュメンタリゼーションに「Defer, Panic, and Recover」と題された新しい記事を追加するものです。この記事は、Goプログラミング言語ブログで2010年8月4日に公開されたものを基にしており、Goにおけるdefer
、panic
、recover
という3つの重要な制御フローメカニズムについて詳細に解説しています。具体的には、これらの機能の動作原理、使用例、およびベストプラクティスが説明されています。
コミット
- コミットハッシュ: 49d82b4ca1a902f5667e845e82440c83287ee633
- 作者: Andrew Gerrand adg@golang.org
- 日付: Mon Dec 12 13:15:29 2011 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/49d82b4ca1a902f5667e845e82440c83287ee633
元コミット内容
doc: add Defer, Panic, and Recover article
Originally published on The Go Programming Language Blog, August 4 2010.
http://blog.golang.org/2010/08/defer-panic-and-recover.html
Update #2547
R=golang-dev, r, r
CC=golang-dev
https://golang.org/cl/5479053
変更の背景
このコミットの背景には、Go言語の公式ドキュメントを充実させ、特にdefer
、panic
、recover
といったGo特有の強力な制御フローメカニズムについて、開発者により深く理解してもらうという目的があります。これらの機能は、他のプログラミング言語にはあまり見られない独特なものであり、リソースのクリーンアップ、エラーハンドリング、予期せぬ実行時エラーからの回復において非常に有用です。
元々Goブログで公開されていた記事を公式ドキュメントに組み込むことで、情報の集約とアクセシビリティの向上が図られています。これにより、Go言語を学ぶ開発者が、これらの重要な概念をより容易に参照し、実践に役立てることができるようになります。
前提知識の解説
このコミットで追加された記事を理解するためには、Go言語の基本的な制御フロー(if
、for
、switch
など)と、Goルーチン(go
ステートメント)に関する知識が前提となります。加えて、以下のGo言語特有の概念について理解を深める必要があります。
defer (遅延実行)
defer
ステートメントは、そのステートメントを含む関数がリターンする直前に、指定された関数呼び出しを遅延実行させるためのものです。これは、リソースの解放(ファイルクローズ、ロック解除など)や、関数の終了時に必ず実行したいクリーンアップ処理を記述するのに非常に便利です。defer
された関数は、関数の正常終了時だけでなく、panic
が発生して関数が異常終了する場合でも実行されます。
panic (パニック)
panic
は、Goプログラムの通常の制御フローを停止させる組み込み関数です。panic
が呼び出されると、現在の関数の実行が直ちに停止し、その関数内でdefer
された関数がすべて実行されます。その後、panic
は呼び出し元関数に伝播し、コールスタックを遡っていきます。もしpanic
がGoルーチンの最上位に到達した場合、プログラムはクラッシュします。panic
は、回復不能なエラーや、プログラムの続行が不可能な状況を示すために使用されます。
recover (回復)
recover
は、panic
が発生したGoルーチンを回復させるための組み込み関数です。recover
はdefer
された関数内でのみ有効です。通常の実行時にはnil
を返しますが、panic
が発生している最中に呼び出されると、panic
に渡された値(エラー情報など)を捕捉し、Goルーチンの通常の実行を再開させます。これにより、プログラム全体のクラッシュを防ぎ、エラーを適切に処理する機会を提供します。
これらの3つの機能は密接に関連しており、特にdefer
とrecover
はpanic
からの回復メカニズムを構築する上で不可欠です。
技術的詳細
追加された記事は、defer
、panic
、recover
の動作について以下の3つのシンプルなルールを提示し、詳細な技術的側面を解説しています。
deferの動作ルール
-
遅延関数の引数は、
defer
ステートメントが評価された時点で評価される。 これは、defer
の対象となる関数に渡される引数の値が、defer
ステートメントが書かれた時点での値に固定されることを意味します。関数が実際に実行されるのは、外側の関数がリターンする直前ですが、引数の評価はその時点ではありません。例:
func a() { i := 0 defer fmt.Println(i) // ここで i は 0 として評価される i++ return // 関数がリターンする際に "0" が出力される }
-
遅延関数呼び出しは、外側の関数がリターンした後に、LIFO(Last In First Out)順で実行される。 複数の
defer
ステートメントがある場合、最後にdefer
された関数が最初に実行され、最初にdefer
された関数が最後に実行されます。例:
func b() { for i := 0; i < 4; i++ { defer fmt.Print(i) // 0, 1, 2, 3 の順で defer される } // 関数がリターンする際に "3210" が出力される }
-
遅延関数は、リターンする関数の名前付き戻り値を読み書きできる。 これは、
defer
された関数が、外側の関数の戻り値を変更できることを意味します。これは、エラーハンドリングや戻り値の最終調整に非常に強力な機能です。例:
func c() (i int) { defer func() { i++ }() // i は戻り値として定義されており、defer 関数内で変更可能 return 1 // 関数がリターンする際に i は 1 となるが、defer 関数でインクリメントされ 2 となる }
panicとrecoverの連携
記事では、panic
とrecover
がどのように連携して、Goルーチンレベルでのエラー回復メカニズムを提供するかが詳細に説明されています。panic
が発生すると、コールスタックを遡りながらdefer
された関数が実行されます。このdefer
された関数内でrecover
が呼び出されると、panic
の伝播が停止し、通常の実行が再開されます。
このメカニズムは、Go標準ライブラリのencoding/json
パッケージなどで実際に使用されています。JSONのデコード中に不正なデータが検出された場合、panic
を発生させてスタックを巻き戻し、トップレベルの関数でrecover
して適切なエラー値を返す、といった処理が行われます。Goの慣習として、パッケージが内部でpanic
を使用する場合でも、外部APIは明示的なエラー戻り値を返すように設計されています。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更または新規追加されています。
doc/Makefile
: 新しい記事articles/defer_panic_recover.html
をビルドターゲットに追加。doc/articles/defer_panic_recover.html
: 「Defer, Panic, and Recover」記事の最終的なHTML出力ファイル。doc/articles/defer_panic_recover.tmpl
: 上記HTMLファイルの生成に使用されるテンプレートファイル。Goのテンプレート構文が含まれています。doc/makehtml
: HTML生成スクリプトの修正。出力ファイルパスの処理が改善されています。doc/progs/defer.go
:defer
の動作ルール(引数の評価タイミング、LIFO順、戻り値の変更)を示すGoコードスニペットが含まれています。また、defer
を使用しないCopyFile
関数の初期バージョンも含まれます。doc/progs/defer2.go
:defer
を使用したCopyFile
関数の改善版と、panic
とrecover
の動作を示すGoコードスニペットが含まれています。doc/progs/run
:doc/progs
ディレクトリ内のGoプログラムをビルドするためのスクリプト。新しいdefer.go
とdefer2.go
がビルド対象に追加されています。doc/tmpltohtml.go
: テンプレートからHTMLを生成するGoプログラムの修正。filepath.Base
を使用してテンプレート名を適切に処理するように変更されています。
コアとなるコードの解説
このコミットの核となるのは、doc/articles/defer_panic_recover.html
とdoc/articles/defer_panic_recover.tmpl
で提供される記事の内容、およびそれを補完するdoc/progs/defer.go
とdoc/progs/defer2.go
内のGoコード例です。
CopyFile
関数の改善例 (defer.go
vs defer2.go
)
記事では、defer
の最も一般的なユースケースとして、リソースのクリーンアップが挙げられています。
defer
を使用しない初期バージョン (doc/progs/defer.go
より抜粋):
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
dst, err := os.Create(dstName)
if err != nil {
return
}
written, err = io.Copy(dst, src)
dst.Close() // ここでクローズ
src.Close() // ここでクローズ
return
}
このコードにはバグがあり、os.Create(dstName)
が失敗した場合、src
ファイルが閉じられずにリソースリークが発生する可能性があります。
defer
を使用した改善バージョン (doc/progs/defer2.go
より抜粋):
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close() // defer を使用して確実にクローズ
dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close() // defer を使用して確実にクローズ
return io.Copy(dst, src)
}
defer
を使用することで、os.Open
やos.Create
の直後にClose()
を記述でき、関数のどのパスを通ってもファイルが確実に閉じられることが保証されます。これにより、コードの可読性と堅牢性が向上します。
panic
とrecover
の動作例 (doc/progs/defer2.go
より抜粋)
package main
import "fmt"
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func() {
if r := recover(); 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 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}
この例では、g
関数が再帰的に呼び出され、i
が3を超えるとpanic
を発生させます。f
関数では、defer
された無名関数内でrecover()
が呼び出されています。
g(0)
から始まり、g(1)
、g(2)
、g(3)
と再帰呼び出しが続きます。g(4)
が呼び出されると、i > 3
の条件が真となり、Panicking!
が出力され、panic("4")
が呼び出されます。panic
が発生すると、g(3)
、g(2)
、g(1)
、g(0)
の順でdefer
された関数が実行されます(LIFO順)。- その後、
panic
はf
関数に伝播し、f
関数内でdefer
された関数が実行されます。 - この
defer
された関数内でrecover()
が呼び出され、panic
の値("4")を捕捉し、Recovered in f 4
が出力されます。 recover
によりpanic
が回復されたため、f
関数は正常に終了し、Returned normally from f.
が出力されます。
この例は、panic
がどのようにコールスタックを遡るか、defer
がどのようにその過程で実行されるか、そしてrecover
がどのようにpanic
を捕捉してプログラムのクラッシュを防ぐかを示しています。
関連リンク
- Go Programming Language Blog: Defer, Panic, and Recover
- Go CL (Change List): https://golang.org/cl/5479053
参考にした情報源リンク
- Go Programming Language Blog: Defer, Panic, and Recover
- Go言語公式ドキュメント (defer): https://go.dev/ref/spec#Defer_statements
- Go言語公式ドキュメント (panic): https://go.dev/ref/spec#Handling_panics
- Go言語公式ドキュメント (recover): https://go.dev/ref/spec#Handling_panics
- Go言語の
encoding/json
パッケージのソースコード (特にdecode.go
): https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/encoding/json/decode.go (これは一般的な参照であり、コミット時点の正確なリンクではない可能性がありますが、概念を理解する上で有用です。)