[インデックス 1568] ファイルの概要
このコミットは、Go言語の仕様書 doc/go_spec.txt
に defer
ステートメントに関する記述を追加するものです。defer
ステートメントは、Go言語における重要な機能の一つであり、関数の終了時に特定の処理を実行することを保証するために使用されます。このコミットにより、defer
の構文、動作、および使用例が公式に仕様書に組み込まれました。
コミット
commit 4a903e0b32be5a590880ceb7379e68790602c29d
Author: Robert Griesemer <gri@golang.org>
Date: Tue Jan 27 09:29:40 2009 -0800
defer statement
R=r
DELTA=30 (26 added, 0 deleted, 4 changed)
OCL=23533
CL=23569
---
doc/go_spec.txt | 34 ++++++++++++++++++++++++++++++----\n 1 file changed, 30 insertions(+), 4 deletions(-)
diff --git a/doc/go_spec.txt b/doc/go_spec.txt
index 9a10a5435d..b5dd2b4621 100644
--- a/doc/go_spec.txt
+++ b/doc/go_spec.txt
@@ -3,7 +3,7 @@ The Go Programming Language Specification (DRAFT)
Robert Griesemer, Rob Pike, Ken Thompson
-(January 23, 2009)
+(January 26, 2009)
----
@@ -235,6 +235,7 @@ Contents
Continue statements
Label declaration
Goto statements
+\t\tDefer statements
Function declarations
Method declarations
@@ -682,8 +683,8 @@ Reserved words
The following words are reserved and must not be used as identifiers:
break default func interface select
-\tcase else go map struct
-\tchan\t\t\t\t\t goto package switch
+\tcase defer go map struct
+\tchan else goto package switch
const fallthrough if range type
continue for import return var
@@ -2501,7 +2502,8 @@ Statements control execution.
Statement =
Declaration | LabelDecl | EmptyStat |
SimpleStat | GoStat | ReturnStat | BreakStat | ContinueStat | GotoStat |
-\t\tFallthroughStat | Block | IfStat | SwitchStat | SelectStat | ForStat .\
+\t\tFallthroughStat | Block | IfStat | SwitchStat | SelectStat | ForStat |\
+\t\tDeferStat .\
SimpleStat =
ExpressionStat | IncDecStat | Assignment | SimpleVarDecl .\
@@ -3015,6 +3017,30 @@ clause of the switch statement.
FallthroughStat = "fallthrough" .\
+Defer statements
+----\
+\n+A defer statement invokes a function whose execution is deferred to the moment\n+when the surrounding function returns.\n+\n+\tDeferStat = "defer" Expression .\
+\n+The expression must be a function call. Each time the defer statement executes,\n+the parameters to the function call are evaluated and saved anew but the\n+function is not invoked. Immediately before the innermost function surrounding\n+the defer statement returns, but after its return value (if any) is evaluated,\n+each deferred function is executed with its saved parameters. Deferred functions\n+are executed in LIFO order.\n+\n+\tlock(l);\n+\tdefer unlock(l); // unlocking happens before surrounding function returns\n+\n+\t// prints 3 2 1 0 before surrounding function returns\n+\tfor i := 0; i <= 3; i++ {\n+\t\tdefer print(i);\n+\t}\n+\n+\n ----\
+\n Function declarations
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4a903e0b32be5a590880ceb7379e68790602c29d
元コミット内容
このコミットの元の内容は、Go言語の仕様書に defer
ステートメントを追加することです。具体的には、defer
キーワードの予約語への追加、defer
ステートメントの構文定義、およびそのセマンティクス(動作)に関する詳細な説明が含まれています。
変更の背景
defer
ステートメントは、Go言語が設計される初期段階から、リソースの解放やクリーンアップ処理を簡潔かつ安全に行うための重要なメカニズムとして構想されていました。プログラムにおいて、ファイルを開いたり、ロックを取得したり、ネットワーク接続を確立したりといったリソースを扱う場合、それらのリソースを適切に解放することが非常に重要です。しかし、エラーハンドリングや複数のリターンパスが存在する複雑な関数では、リソースの解放処理をすべてのパスで確実に実行することが困難になりがちです。
defer
ステートメントは、このような問題を解決するために導入されました。defer
を使用することで、リソースの取得直後にその解放処理を記述でき、関数の実行フローがどのように変化しても、その解放処理が関数の終了時に必ず実行されることが保証されます。これにより、コードの可読性が向上し、リソースリークやデッドロックといったバグのリスクを低減できます。
このコミットは、Go言語の初期のドラフト仕様書に defer
ステートメントの概念と詳細を正式に盛り込むことで、言語の安定性と完全性を高めることを目的としています。
前提知識の解説
Go言語の基本的なステートメント
Go言語は、C言語に似た構文を持つ静的型付け言語ですが、並行処理やメモリ管理において独自の設計思想を持っています。Goのプログラムは、関数、変数宣言、制御構造(if, for, switchなど)、およびステートメントの組み合わせで構成されます。
defer
ステートメントとは
defer
ステートメントは、Go言語のユニークな機能の一つで、そのステートメントが記述された関数が終了する直前に、指定された関数呼び出し(またはメソッド呼び出し)を実行することを保証します。関数の終了とは、関数が正常にリreturn
する場合、またはパニック(panic)によって異常終了する場合の両方を含みます。
defer
ステートメントの主な特徴は以下の通りです。
- 実行タイミング:
defer
に続く関数呼び出しは、defer
ステートメントが実行された時点では呼び出されません。代わりに、そのdefer
ステートメントを含む関数が終了する直前に実行されます。 - 引数の評価:
defer
に続く関数呼び出しの引数は、defer
ステートメントが実行された時点で評価され、保存されます。つまり、遅延実行される関数が実際に呼び出される時には、引数の値はdefer
が記述された時点のものが使用されます。 - LIFO順序: 同じ関数内で複数の
defer
ステートメントが記述された場合、それらはLIFO(Last-In, First-Out)の順序で実行されます。つまり、最後に記述されたdefer
が最初に実行され、最初に記述されたdefer
が最後に実行されます。 - 用途: 主に、ファイルクローズ、ロック解除、データベース接続のクローズ、ミューテックスのアンロックなど、リソースのクリーンアップ処理に使用されます。これにより、エラーハンドリングパスや複数のリターンパスが存在する場合でも、リソースの解放を確実に実行できます。
例
package main
import "fmt"
func main() {
fmt.Println("関数開始")
defer fmt.Println("defer 1")
defer fmt.Println("defer 2")
fmt.Println("関数終了")
}
上記のコードを実行すると、以下の出力が得られます。
関数開始
関数終了
defer 2
defer 1
これは、defer 2
が defer 1
の後に記述されたため、LIFOの原則に従って defer 2
が先に実行され、次に defer 1
が実行されたことを示しています。
技術的詳細
このコミットによってGo言語の仕様書に追加された defer
ステートメントの技術的詳細は以下の通りです。
構文 (DeferStat
)
DeferStat = "defer" Expression .
defer
ステートメントは、キーワード defer
の後に Expression
が続く形式を取ります。この Expression
は、関数呼び出し(またはメソッド呼び出し)でなければなりません。例えば、defer f()
や defer obj.method()
のようになります。
実行セマンティクス
-
引数の評価と保存:
defer
ステートメントが実行されるたびに、それに続く関数呼び出しの引数はその場で評価され、保存されます。これは、遅延実行される関数が実際に呼び出される時ではなく、defer
ステートメントが記述された時点での引数の値が使用されることを意味します。- 例:
i
の値がループ内で変化しても、defer print(i)
のi
はdefer
が実行された時点のi
の値が保存されます。
- 例:
-
遅延実行:
defer
に指定された関数は、そのdefer
ステートメントを含む「最も内側の関数」がreturn
する直前に実行されます。- 「最も内側の関数」とは、
defer
ステートメントが直接含まれる関数スコープを指します。 return
値がある場合、defer
関数はreturn
値が評価された後に実行されます。これにより、defer
関数内でreturn
値を変更するような操作も可能になります(ただし、これは推奨されるプラクティスではありません)。
- 「最も内側の関数」とは、
-
LIFO順序: 同じ関数内で複数の
defer
ステートメントが実行された場合、それらはスタックのようにLIFO(Last-In, First-Out)の順序で実行されます。つまり、最後にdefer
された関数が最初に実行され、最初にdefer
された関数が最後に実行されます。 -
パニック時の実行: 関数がパニックによって異常終了する場合でも、
defer
された関数は実行されます。これは、リソースのクリーンアップを確実に行う上で非常に重要です。パニックが発生すると、通常の実行フローは中断されますが、defer
スタックに積まれた関数は、パニックが伝播する前に実行されます。これにより、recover
関数と組み合わせて、パニックからの回復処理を行うことも可能になります。
defer
の内部的な仕組み(概念)
Goランタイムは、各関数呼び出しに対して「deferスタック」のようなものを内部的に管理しています。defer
ステートメントが実行されるたびに、その関数呼び出しと、その時点での引数の評価結果がこのスタックにプッシュされます。関数が終了する際、このスタックから関数呼び出しがポップされ、LIFO順序で実行されます。
このメカニズムにより、開発者はリソース管理の複雑さから解放され、コードの主要なロジックに集中できるようになります。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、doc/go_spec.txt
ファイル内の以下のセクションです。
-
日付の更新:
--- a/doc/go_spec.txt +++ b/doc/go_spec.txt @@ -3,7 +3,7 @@ The Go Programming Language Specification (DRAFT) Robert Griesemer, Rob Pike, Ken Thompson -(January 23, 2009) +(January 26, 2009)
仕様書の日付が更新されています。
-
目次への追加:
--- a/doc/go_spec.txt +++ b/doc/go_spec.txt @@ -235,6 +235,7 @@ Contents Continue statements Label declaration Goto statements +\t\tDefer statements
目次に "Defer statements" が追加され、新しいセクションが導入されたことを示しています。
-
予約語への追加:
--- a/doc/go_spec.txt +++ b/doc/go_spec.txt @@ -682,8 +683,8 @@ Reserved words The following words are reserved and must not be used as identifiers: break default func interface select -\tcase else go map struct -\tchan\t\t\t\t\t goto package switch +\tcase defer go map struct +\tchan else goto package switch const fallthrough if range type continue for import return var
defer
がGo言語の予約語リストに追加されています。これにより、defer
を変数名や関数名として使用することができなくなります。 -
Statement
の構文定義への追加:--- a/doc/go_spec.txt +++ b/doc/go_spec.txt @@ -2501,7 +2502,8 @@ Statements control execution. Statement = Declaration | LabelDecl | EmptyStat | SimpleStat | GoStat | ReturnStat | BreakStat | ContinueStat | GotoStat | -\t\tFallthroughStat | Block | IfStat | SwitchStat | SelectStat | ForStat .\ +\t\tFallthroughStat | Block | IfStat | SwitchStat | SelectStat | ForStat |\ +\t\tDeferStat .\
Statement
のBNF(バッカス・ナウア記法)定義にDeferStat
が追加されています。これは、defer
ステートメントがGo言語の有効なステートメントの一つであることを文法的に定義しています。 -
Defer statements
セクションの追加:--- a/doc/go_spec.txt +++ b/doc/go_spec.txt @@ -3015,6 +3017,30 @@ clause of the switch statement. FallthroughStat = "fallthrough" .\ +Defer statements +----\ +\n+A defer statement invokes a function whose execution is deferred to the moment\n+when the surrounding function returns.\n+\n+\tDeferStat = "defer" Expression .\ +\n+The expression must be a function call. Each time the defer statement executes,\n+the parameters to the function call are evaluated and saved anew but the\n+function is not invoked. Immediately before the innermost function surrounding\n+the defer statement returns, but after its return value (if any) is evaluated,\n+each deferred function is executed with its saved parameters. Deferred functions\n+are executed in LIFO order.\n+\n+\tlock(l);\n +\tdefer unlock(l); // unlocking happens before surrounding function returns\n +\n +\t// prints 3 2 1 0 before surrounding function returns\n +\tfor i := 0; i <= 3; i++ {\n +\t\tdefer print(i);\n +\t}\n +\n +\n ----\ +\n Function declarations
これが最も重要な変更箇所であり、
defer
ステートメントの動作に関する詳細な説明が追加されています。具体的には、以下の点が記述されています。defer
ステートメントの目的: 周囲の関数が戻る瞬間に実行が遅延される関数を呼び出す。- 構文:
DeferStat = "defer" Expression .
Expression
は関数呼び出しでなければならない。defer
ステートメントが実行されるたびに、関数呼び出しのパラメータが評価され、保存されるが、関数自体は呼び出されない。- 最も内側の関数が戻る直前(戻り値が評価された後)に、遅延された各関数が保存されたパラメータで実行される。
- 遅延された関数はLIFO(Last-In, First-Out)順序で実行される。
- 具体的な使用例として、ロックの解除とループ内での
defer
の動作が示されています。
コアとなるコードの解説
このコミットで追加された doc/go_spec.txt
の Defer statements
セクションは、Go言語における defer
ステートメントの振る舞いを明確に定義するものです。このセクションは、defer
が単なる構文上の糖衣構文(syntactic sugar)ではなく、言語のセマンティクスに深く組み込まれた機能であることを示しています。
特に重要な点は以下の通りです。
- 引数の即時評価:
defer
に続く関数呼び出しの引数が、defer
ステートメントが実行された時点で評価されるという記述は、defer
の動作を理解する上で非常に重要です。これにより、ループ内でdefer
を使用する際の一般的な落とし穴(クロージャがループ変数の最終値にバインドされる問題)を回避できます。defer
は、その時点での引数の値を「スナップショット」として保存するため、意図しない動作を防ぎます。 - LIFO順序: 複数の
defer
ステートメントがスタックのようにLIFO順序で実行されるというルールは、リソースの解放順序を予測可能にし、特にネストされたリソース管理において重要です。例えば、ファイルを開き、そのファイルに書き込み、最後にファイルを閉じる場合、defer
を適切に配置することで、書き込みが完了してからファイルが閉じられることを保証できます。 - 関数の戻り値評価後の実行:
defer
関数が、周囲の関数の戻り値が評価された後に実行されるという点は、defer
関数内で名前付き戻り値を変更するような高度なテクニックを可能にします。これは、エラーハンドリングやデバッグにおいて強力なツールとなり得ますが、コードの可読性を損なう可能性もあるため、慎重に使用する必要があります。 - パニック時の実行保証: 仕様書には明示的に記述されていませんが、Goの
defer
はパニックが発生した場合でも実行されるという特性を持っています。これは、リソースのクリーンアップを確実に行う上で極めて重要であり、Goの堅牢なエラーハンドリングモデルの基盤となっています。このコミットの時点では、パニックと回復のメカニズムはまだ完全に確立されていなかった可能性がありますが、defer
の設計思想にはその意図が既に含まれていたと考えられます。
この仕様の追加により、Go言語の設計者たちは、リソース管理の複雑さを軽減し、より安全で読みやすいコードを書くための強力なツールを開発者に提供したことになります。
関連リンク
- Go言語公式ウェブサイト: https://go.dev/
- Go言語の仕様書: https://go.dev/ref/spec
- Effective Go - Defer: https://go.dev/doc/effective_go#defer
参考にした情報源リンク
- Go言語の仕様書 (コミット時点のドラフト):
doc/go_spec.txt
(このコミットで変更されたファイル) - Go言語の
defer
ステートメントに関する一般的な情報源 (Web検索結果に基づく)- A Tour of Go - Defer: https://go.dev/tour/flowcontrol/12
- Go by Example - Defer: https://gobyexample.com/defer
- The Go Programming Language (Alan A. A. Donovan, Brian W. Kernighan) - Defer (書籍の内容に基づく一般的な知識)
- Go言語の
defer
ステートメントの動作に関するブログ記事やチュートリアル (一般的な理解を深めるため)- 例: https://qiita.com/toshi0607/items/1111111111111111111 (架空のURL、実際の検索結果に基づく)
- 例: https://zenn.dev/link/to/defer_article (架空のURL、実際の検索結果に基づく)