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

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

このコミットは、Go言語の公式仕様書(doc/go_spec.html)に対する変更であり、goステートメントおよびdeferステートメントにおける組み込み関数の使用と、呼び出しの括弧の使用に関する曖昧さを解消することを目的としています。具体的には、組み込み関数の使用が式ステートメントと同様に制限されること、およびgodeferの呼び出しが括弧で囲むことができないことを明記しています。

コミット

commit 25dd00295c54fa23a545bfdd27824ac403ceba84
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Nov 29 11:46:25 2012 -0800

    spec: clarify use of built-ins in go/defer statements
    
    1) Built-ins are restricted like for expression statements.
       This documents the status quo.
    
    2) Calls cannot be parenthesized. The spec is not clear. gccgo
       permits it already, gc doesn't. Be explicit in the spec.
    
     Fixes #4462.
    
    R=rsc, iant, r, ken, lvd
    CC=golang-dev
    https://golang.org/cl/6861043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/25dd00295c54fa23a545bfdd27824ac403ceba84

元コミット内容

このコミットは、Go言語の仕様書において、goおよびdeferステートメント内での組み込み関数の扱いと、関数呼び出しの構文に関する記述を明確化するものです。

  1. 組み込み関数の制限の明確化: 組み込み関数(例: len, cap, make, new, panic, recover, print, println, append, copy, delete, complex, real, imag)は、goおよびdeferステートメント内で使用される場合、式ステートメント(Expression statements)と同様の制限を受けることを明記します。これは既存の動作を文書化したものです。
  2. 呼び出しの括弧に関する明確化: goおよびdeferステートメント内の関数呼び出しは、括弧で囲むことができないことを明記します。これまでの仕様ではこの点が不明確であり、gccgoコンパイラはこれを許可していたのに対し、公式のgcコンパイラは許可していませんでした。この変更により、仕様が統一され、両コンパイラ間の動作の不一致が解消されます。

この変更は、Go言語のIssue #4462を修正するものです。

変更の背景

このコミットの背景には、Go言語の仕様の曖昧さと、それによって生じる異なるコンパイラ実装(gcgccgo)間の動作の不一致がありました。

  1. 組み込み関数の使用に関する曖昧さ: Go言語には、lenmakeなどの組み込み関数が存在します。これらの関数は通常の関数とは異なり、特定の文脈でのみ使用が許可されています。特に、式ステートメントではその使用に制限があることが知られていましたが、goステートメントやdeferステートメント内でこれらの組み込み関数を呼び出す際の具体的な制限が仕様書で明確に記述されていませんでした。これにより、開発者が予期しない挙動に遭遇したり、コンパイラの実装者間で解釈のずれが生じる可能性がありました。このコミットは、既存の動作(式ステートメントと同様の制限)を明文化することで、この曖昧さを解消しようとしています。

  2. 関数呼び出しの括弧に関するコンパイラ間の不一致: Go言語では、go f()defer g()のように、godeferの後に直接関数呼び出しを記述するのが一般的です。しかし、go (f())defer (g())のように、関数呼び出し全体を括弧で囲むことが許されるのかどうか、仕様書では明確にされていませんでした。この曖昧さのため、gccgoコンパイラはこのような括弧付きの呼び出しを許可していたのに対し、Goの公式コンパイラであるgcはこれを許可していませんでした。この不一致は、Goプログラムの移植性や、異なるコンパイラを使用する際の開発者の混乱を招く可能性がありました。Issue #4462は、このコンパイラ間の動作の不一致を指摘し、仕様の明確化を求めたものです。このコミットは、括弧付きの呼び出しを明示的に禁止することで、仕様を統一し、コンパイラ間の動作を一致させることを目的としています。

これらの変更は、Go言語の仕様をより堅牢で明確にし、開発者とコンパイラ実装者の双方にとって予測可能な言語動作を保証するために行われました。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念について基本的な知識が必要です。

  1. goステートメント (Go statement):

    • Go言語における並行処理の基本要素です。
    • goキーワードの後に続く関数呼び出しを、新しいゴルーチン(goroutine)として独立した並行実行スレッドで実行します。
    • 例: go myFunction()myFunction を新しいゴルーチンで実行します。
    • goステートメントは、呼び出し元のゴルーチンとは独立して実行されるため、非同期処理を実現するために使用されます。
  2. deferステートメント (Defer statement):

    • deferキーワードの後に続く関数呼び出しを、そのdeferステートメントが記述された関数が終了する直前(returnする直前、またはパニックが発生する直前)に実行するようにスケジュールします。
    • 例: defer file.Close() は、関数が終了する際にfile.Close()が確実に呼び出されるようにします。
    • 主にリソースの解放(ファイル、ネットワーク接続、ロックなど)や、クリーンアップ処理を確実に行うために使用されます。deferされた関数はLIFO(後入れ先出し)の順序で実行されます。
  3. 組み込み関数 (Built-in functions):

    • Go言語に最初から組み込まれている特殊な関数群です。これらは通常の関数とは異なり、インポートなしで使用でき、特定の型に依存しない汎用的な操作を提供します。
    • 例:
      • len, cap: スライス、配列、マップ、チャネルなどの長さや容量を取得。
      • make: スライス、マップ、チャネルを作成し初期化。
      • new: 型のゼロ値を持つ新しいメモリを割り当て、そのポインタを返す。
      • panic, recover: エラーハンドリング機構。
      • print, println: デバッグ用の出力。
      • append, copy: スライス操作。
      • delete: マップから要素を削除。
      • complex, real, imag: 複素数操作。
    • これらの関数は、通常の関数呼び出しとは異なる特別なコンテキストで扱われることがあり、特定の場所での使用が制限される場合があります。
  4. 式ステートメント (Expression statements):

    • Go言語のステートメントの一種で、副作用を持つ式(関数呼び出し、チャネル送受信、インクリメント/デクリメントなど)を単独で記述するものです。
    • Goの仕様では、式ステートメントとして許可される式には制限があります。例えば、単なる数値リテラルや変数名だけでは式ステートメントにはなれません。必ず何らかの副作用を伴う必要があります。
    • 組み込み関数の中には、式ステートメントとして単独で使用できないものがあります(例: len(s)は値を返すだけで副作用がないため、単独では式ステートメントになれません。_ = len(s)のように代入するか、fmt.Println(len(s))のように別の関数に渡す必要があります)。
  5. Goコンパイラ (gcgccgo):

    • gc: Go言語の公式かつ主要なコンパイラです。Goのソースコードをネイティブバイナリにコンパイルします。Goチームによって開発・保守されています。
    • gccgo: GCC(GNU Compiler Collection)のフロントエンドとして実装されたGoコンパイラです。GCCのインフラストラクチャを利用しており、gcとは異なる実装アプローチを取っています。歴史的に、gcgccgoの間でGo言語の仕様の解釈や実装にわずかな違いが生じることがありました。このコミットで修正される問題もその一例です。

これらの概念を理解することで、コミットがGo言語の仕様のどの部分を、なぜ、どのように変更したのかを深く把握することができます。

技術的詳細

このコミットは、Go言語の仕様書(doc/go_spec.html)の2つの主要なセクション、すなわち「Go statements」と「Defer statements」に焦点を当て、それぞれのステートメント内で許可される式について、より厳密なルールを導入しています。

1. 組み込み関数の制限の明確化

変更前は、goおよびdeferステートメント内の関数呼び出しについて、組み込み関数の扱いに関する具体的な制限が明記されていませんでした。このコミットにより、以下の記述が追加されました。

Calls of built-in functions are restricted as for expression statements.

これは、godeferの引数として組み込み関数を呼び出す場合、その組み込み関数が単独の「式ステートメント」として有効である必要があることを意味します。

「式ステートメントと同様に制限される」とは? Go言語の仕様では、式ステートメントとして許可される式は、何らかの副作用を持つものに限られます。例えば、以下の組み込み関数は副作用を持たないため、単独で式ステートメントとして使用することはできません。

  • len, cap: 長さや容量を返すだけで、状態を変更しない。
  • make, new: 新しい値を生成するが、既存の状態を変更する副作用はない。
  • complex, real, imag: 複素数演算の結果を返すだけで、副作用はない。

したがって、例えば go len(mySlice)defer new(MyStruct) のようなコードは、この変更によって明示的に不正となります。これらの組み込み関数は値を返すだけで、ゴルーチンとして実行したり、遅延実行したりする意味がないため、これは論理的な制限です。一方で、panic("error")copy(dst, src) のように副作用を持つ組み込み関数は、引き続きgodeferステートメント内で使用できます。

この変更は、既存のコンパイラの動作(gcコンパイラは既にこのような使用を許可していなかった)を仕様書に反映させ、言語の整合性を高めるものです。

2. 呼び出しの括弧に関する明確化

変更前は、goおよびdeferステートメントの引数となる関数呼び出しを括弧で囲むことが許可されるかどうかが不明確でした。

  • 変更前: The expression must be a call.
  • 変更後: The expression must be a function or method call; it cannot be parenthesized.

この変更により、go (f())defer (g()) のような構文は、Go言語の仕様上、明確に禁止されることになりました。

なぜこの変更が必要だったのか? Go言語のコンテキストでは、go f()defer g() のように、godeferの後に直接関数呼び出しが続くのが慣例的です。括弧で囲むことは、他の言語(例えばJavaScriptの即時実行関数式 (function(){})())では一般的かもしれませんが、Goのgodeferの構文とは合致しませんでした。

最も重要な点は、Goの異なるコンパイラ実装間でこの挙動に不一致があったことです。

  • gccgo: 括弧で囲まれた呼び出しを許可していました。
  • gc (公式コンパイラ): 括弧で囲まれた呼び出しを許可していませんでした。

この不一致は、Goプログラムの移植性を損ない、開発者を混乱させる原因となっていました。Issue #4462は、このコンパイラ間の動作のずれを指摘し、仕様の明確化を求めたものです。このコミットは、括弧の使用を明示的に禁止することで、仕様を統一し、すべてのGoコンパイラが同じ動作をするように強制します。これにより、Goコードの予測可能性と一貫性が向上します。

まとめ

このコミットは、Go言語の仕様をより厳密かつ明確にすることで、言語の堅牢性を高め、異なるコンパイラ実装間での動作の一貫性を保証することを目的としています。特に、組み込み関数の使用に関する暗黙のルールを明文化し、関数呼び出しの括弧の使用に関する曖昧さを解消した点が重要です。

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

このコミットによる変更は、Go言語の公式仕様書である doc/go_spec.html ファイルに集中しています。

--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -1,6 +1,6 @@
 <!--{
 	"Title": "The Go Programming Language Specification",
-	"Subtitle": "Version of November 26, 2012",
+	"Subtitle": "Version of November 29, 2012",
 	"Path": "/ref/spec"
 }-->
 
@@ -4431,7 +4431,7 @@ for w := range ch {\n <h3 id=\"Go_statements\">Go statements</h3>\n \n <p>\n-A \"go\" statement starts the execution of a function or method call\n+A \"go\" statement starts the execution of a function call\n as an independent concurrent thread of control, or <i>goroutine</i>,\n within the same address space.\n </p>\n@@ -4441,7 +4441,12 @@ GoStmt = "go" Expression .\n </pre>\n \n <p>\n-The expression must be a call.\n+The expression must be a function or method call; it cannot be parenthesized.\n+Calls of built-in functions are restricted as for\n+<a href=\"#Expression_statements\">expression statements</a>.\n+</p>\n+\n+<p>\n The function value and parameters are\n <a href=\"#Calls\">evaluated as usual</a>\n in the calling goroutine, but\n@@ -4758,7 +4763,12 @@ DeferStmt = "defer" Expression .\n </pre>\n \n <p>\n-The expression must be a function or method call.\n+The expression must be a function or method call; it cannot be parenthesized.\n+Calls of built-in functions are restricted as for\n+<a href=\"#Expression_statements\">expression statements</a>.\n+</p>\n+\n+<p>\n Each time the \"defer\" statement\n executes, the function value and parameters to the call are\n <a href=\"#Calls\">evaluated as usual</a>\n```

## コアとなるコードの解説

変更は主に`doc/go_spec.html`内の2つのセクションにあります。

1.  **ファイルのメタデータ更新**:
    ```diff
    --- a/doc/go_spec.html
    +++ b/doc/go_spec.html
    @@ -1,6 +1,6 @@
     <!--{
     	"Title": "The Go Programming Language Specification",
    -	"Subtitle": "Version of November 26, 2012",
    +	"Subtitle": "Version of November 29, 2012",
     	"Path": "/ref/spec"
     }-->
    ```
    これは、仕様書のバージョン日付を「November 26, 2012」から「November 29, 2012」に更新したものです。これはコミットが行われた日付を反映した、文書の一般的な更新です。

2.  **`Go statements` セクションの変更**:
    ```diff
    @@ -4431,7 +4431,7 @@ for w := range ch {\n  <h3 id=\"Go_statements\">Go statements</h3>\n  \n  <p>\n-A \"go\" statement starts the execution of a function or method call\n+A \"go\" statement starts the execution of a function call\n  as an independent concurrent thread of control, or <i>goroutine</i>,\n  within the same address space.\n  </p>\n    ```
    *   最初の変更は、`A "go" statement starts the execution of a function or method call` から `A "go" statement starts the execution of a function call` への修正です。これは「またはメソッド呼び出し」という冗長な表現を削除し、より簡潔に「関数呼び出し」と表現するように変更されました。Go言語ではメソッド呼び出しも広義の関数呼び出しとして扱われるため、意味的な変更はありません。

    ```diff
    @@ -4441,7 +4441,12 @@ GoStmt = "go" Expression .\n  </pre>\n  \n  <p>\n-The expression must be a call.\n+The expression must be a function or method call; it cannot be parenthesized.\n+Calls of built-in functions are restricted as for\n+<a href=\"#Expression_statements\">expression statements</a>.\n+</p>\n+\n+<p>\n  The function value and parameters are\n  <a href=\"#Calls\">evaluated as usual</a>\n  in the calling goroutine, but\n    ```
    *   ここが最も重要な変更点です。
        *   元の記述 `The expression must be a call.` は非常に一般的でした。
        *   新しい記述 `The expression must be a function or method call; it cannot be parenthesized.` は、まず「関数またはメソッド呼び出し」であることを再確認し、その後に **`it cannot be parenthesized.`** (括弧で囲むことはできない)という明確な制約を追加しています。これにより、`go (f())` のような構文が不正であることが明示されました。
        *   さらに、**`Calls of built-in functions are restricted as for <a href="#Expression_statements">expression statements</a>.`** という文が追加されました。これは、`go`ステートメント内で組み込み関数を呼び出す場合、その組み込み関数が単独の式ステートメントとして有効である必要があることを明確にしています。これにより、`go len(s)` のような副作用のない組み込み関数の呼び出しが不正であることが明示されました。

3.  **`Defer statements` セクションの変更**:
    ```diff
    @@ -4758,7 +4763,12 @@ DeferStmt = "defer" Expression .\n  </pre>\n  \n  <p>\n-The expression must be a function or method call.\n+The expression must be a function or method call; it cannot be parenthesized.\n+Calls of built-in functions are restricted as for\n+<a href=\"#Expression_statements\">expression statements</a>.\n+</p>\n+\n+<p>\n  Each time the \"defer\" statement\n  executes, the function value and parameters to the call are\n  <a href=\"#Calls\">evaluated as usual</a>\n    ```
    *   `go`ステートメントの変更と全く同じ変更が`defer`ステートメントにも適用されています。
        *   `The expression must be a function or method call; it cannot be parenthesized.` (括弧で囲むことはできない)
        *   `Calls of built-in functions are restricted as for <a href="#Expression_statements">expression statements</a>.` (組み込み関数の呼び出しは式ステートメントと同様に制限される)
    *   これにより、`defer (g())` や `defer new(MyStruct)` のような構文が不正であることが明示されました。

これらの変更は、Go言語の仕様をより厳密にし、コンパイラの実装(特に`gc`と`gccgo`間の不一致)を統一することを目的としています。

## 関連リンク

*   **Go言語の仕様書 (The Go Programming Language Specification)**:
    *   [https://golang.org/ref/spec](https://golang.org/ref/spec)
    *   このコミットが変更を加えた文書の最新版です。
*   **Go Issue #4462: spec: clarify go/defer statements**:
    *   [https://github.com/golang/go/issues/4462](https://github.com/golang/go/issues/4462)
    *   このコミットが修正した元の問題報告です。コンパイラ間の不一致と仕様の曖昧さが議論されています。
*   **Gerrit Change-ID: 6861043**:
    *   [https://golang.org/cl/6861043](https://golang.org/cl/6861043)
    *   このコミットに対応するGerritの変更リストです。レビューの経緯や詳細な議論を確認できます。

## 参考にした情報源リンク

*   **Go言語の仕様書 - Expression statements**:
    *   [https://golang.org/ref/spec#Expression_statements](https://golang.org/ref/spec#Expression_statements)
    *   組み込み関数の制限を理解するために参照しました。
*   **Go言語の仕様書 - Built-in functions**:
    *   [https://golang.org/ref/spec#Built-in_functions](https://golang.org/ref/spec#Built-in_functions)
    *   Goの組み込み関数の一覧と説明を理解するために参照しました。
*   **Go言語の仕様書 - Go statements**:
    *   [https://golang.org/ref/spec#Go_statements](https://golang.org/ref/spec#Go_statements)
    *   `go`ステートメントの定義を理解するために参照しました。
*   **Go言語の仕様書 - Defer statements**:
    *   [https://golang.org/ref/spec#Defer_statements](https://golang.org/ref/spec#Defer_statements)
    *   `defer`ステートメントの定義を理解するために参照しました。
*   **Go言語の仕様書 - Calls**:
    *   [https://golang.org/ref/spec#Calls](https://golang.org/ref/spec#Calls)
    *   関数呼び出しの評価順序などを理解するために参照しました。
*   **Go言語のコンパイラについて (gc vs gccgo)**:
    *   Go言語の公式ドキュメントやコミュニティの議論(例: Stack Overflow, Go blog posts)から、`gc`と`gccgo`の歴史的背景と違いに関する一般的な知識を参照しました。