Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 1568] ファイルの概要

このコミットは、Go言語の仕様書 doc/go_spec.txtdefer ステートメントに関する記述を追加するものです。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 ステートメントの主な特徴は以下の通りです。

  1. 実行タイミング: defer に続く関数呼び出しは、defer ステートメントが実行された時点では呼び出されません。代わりに、その defer ステートメントを含む関数が終了する直前に実行されます。
  2. 引数の評価: defer に続く関数呼び出しの引数は、defer ステートメントが実行された時点で評価され、保存されます。つまり、遅延実行される関数が実際に呼び出される時には、引数の値は defer が記述された時点のものが使用されます。
  3. LIFO順序: 同じ関数内で複数の defer ステートメントが記述された場合、それらはLIFO(Last-In, First-Out)の順序で実行されます。つまり、最後に記述された defer が最初に実行され、最初に記述された defer が最後に実行されます。
  4. 用途: 主に、ファイルクローズ、ロック解除、データベース接続のクローズ、ミューテックスのアンロックなど、リソースのクリーンアップ処理に使用されます。これにより、エラーハンドリングパスや複数のリターンパスが存在する場合でも、リソースの解放を確実に実行できます。

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 2defer 1 の後に記述されたため、LIFOの原則に従って defer 2 が先に実行され、次に defer 1 が実行されたことを示しています。

技術的詳細

このコミットによってGo言語の仕様書に追加された defer ステートメントの技術的詳細は以下の通りです。

構文 (DeferStat)

DeferStat = "defer" Expression .

defer ステートメントは、キーワード defer の後に Expression が続く形式を取ります。この Expression は、関数呼び出し(またはメソッド呼び出し)でなければなりません。例えば、defer f()defer obj.method() のようになります。

実行セマンティクス

  1. 引数の評価と保存: defer ステートメントが実行されるたびに、それに続く関数呼び出しの引数はその場で評価され、保存されます。これは、遅延実行される関数が実際に呼び出される時ではなく、defer ステートメントが記述された時点での引数の値が使用されることを意味します。

    • 例: i の値がループ内で変化しても、defer print(i)idefer が実行された時点の i の値が保存されます。
  2. 遅延実行: defer に指定された関数は、その defer ステートメントを含む「最も内側の関数」が return する直前に実行されます。

    • 「最も内側の関数」とは、defer ステートメントが直接含まれる関数スコープを指します。
    • return 値がある場合、defer 関数は return 値が評価された後に実行されます。これにより、defer 関数内で return 値を変更するような操作も可能になります(ただし、これは推奨されるプラクティスではありません)。
  3. LIFO順序: 同じ関数内で複数の defer ステートメントが実行された場合、それらはスタックのようにLIFO(Last-In, First-Out)の順序で実行されます。つまり、最後に defer された関数が最初に実行され、最初に defer された関数が最後に実行されます。

  4. パニック時の実行: 関数がパニックによって異常終了する場合でも、defer された関数は実行されます。これは、リソースのクリーンアップを確実に行う上で非常に重要です。パニックが発生すると、通常の実行フローは中断されますが、defer スタックに積まれた関数は、パニックが伝播する前に実行されます。これにより、recover 関数と組み合わせて、パニックからの回復処理を行うことも可能になります。

defer の内部的な仕組み(概念)

Goランタイムは、各関数呼び出しに対して「deferスタック」のようなものを内部的に管理しています。defer ステートメントが実行されるたびに、その関数呼び出しと、その時点での引数の評価結果がこのスタックにプッシュされます。関数が終了する際、このスタックから関数呼び出しがポップされ、LIFO順序で実行されます。

このメカニズムにより、開発者はリソース管理の複雑さから解放され、コードの主要なロジックに集中できるようになります。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更箇所は、doc/go_spec.txt ファイル内の以下のセクションです。

  1. 日付の更新:

    --- 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)
    

    仕様書の日付が更新されています。

  2. 目次への追加:

    --- 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" が追加され、新しいセクションが導入されたことを示しています。

  3. 予約語への追加:

    --- 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 を変数名や関数名として使用することができなくなります。

  4. 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言語の有効なステートメントの一つであることを文法的に定義しています。

  5. 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.txtDefer statements セクションは、Go言語における defer ステートメントの振る舞いを明確に定義するものです。このセクションは、defer が単なる構文上の糖衣構文(syntactic sugar)ではなく、言語のセマンティクスに深く組み込まれた機能であることを示しています。

特に重要な点は以下の通りです。

  • 引数の即時評価: defer に続く関数呼び出しの引数が、defer ステートメントが実行された時点で評価されるという記述は、defer の動作を理解する上で非常に重要です。これにより、ループ内で defer を使用する際の一般的な落とし穴(クロージャがループ変数の最終値にバインドされる問題)を回避できます。defer は、その時点での引数の値を「スナップショット」として保存するため、意図しない動作を防ぎます。
  • LIFO順序: 複数の defer ステートメントがスタックのようにLIFO順序で実行されるというルールは、リソースの解放順序を予測可能にし、特にネストされたリソース管理において重要です。例えば、ファイルを開き、そのファイルに書き込み、最後にファイルを閉じる場合、defer を適切に配置することで、書き込みが完了してからファイルが閉じられることを保証できます。
  • 関数の戻り値評価後の実行: defer 関数が、周囲の関数の戻り値が評価された後に実行されるという点は、defer 関数内で名前付き戻り値を変更するような高度なテクニックを可能にします。これは、エラーハンドリングやデバッグにおいて強力なツールとなり得ますが、コードの可読性を損なう可能性もあるため、慎重に使用する必要があります。
  • パニック時の実行保証: 仕様書には明示的に記述されていませんが、Goの defer はパニックが発生した場合でも実行されるという特性を持っています。これは、リソースのクリーンアップを確実に行う上で極めて重要であり、Goの堅牢なエラーハンドリングモデルの基盤となっています。このコミットの時点では、パニックと回復のメカニズムはまだ完全に確立されていなかった可能性がありますが、defer の設計思想にはその意図が既に含まれていたと考えられます。

この仕様の追加により、Go言語の設計者たちは、リソース管理の複雑さを軽減し、より安全で読みやすいコードを書くための強力なツールを開発者に提供したことになります。

関連リンク

参考にした情報源リンク

  • Go言語の仕様書 (コミット時点のドラフト): doc/go_spec.txt (このコミットで変更されたファイル)
  • Go言語の defer ステートメントに関する一般的な情報源 (Web検索結果に基づく)