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

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

コミット

commit 871698136d7a6452267b90e06d7cab4fc1f7cfea
Author: Robert Griesemer <gri@golang.org>
Date:   Mon Mar 3 20:07:34 2014 -0800

    spec: clarify what is considered a function call for len/cap special case
    
    gccgo considers built-in function calls returning a constant not as function call (issue 7386)
    go/types considers any call (regular or built-in) as a function call
    
    The wording and examples clarify that only "function calls" that are issued
    at run-time (and thus do not result in a constant result) are considered
    function calls in this case.
    
    gc is inconsistent (issue 7385)
    gccgo already interprets the spec accordingly and issue 7386 is moot.
    go/types considers all calls (constant or not) as function calls (issue 7457).
    
    Fixes #7387.
    Fixes #7386.
    
    LGTM=r, rsc, iant
    R=r, rsc, iant, ken
    CC=golang-codereviews
    https://golang.org/cl/66860046

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

https://github.com/golang/go/commit/871698136d7a6452267b90e06d7cab4fc1f7cfea

元コミット内容

このコミットは、Go言語の仕様書(doc/go_spec.html)を更新し、lenおよびcap組み込み関数が定数式となる条件における「関数呼び出し」の定義を明確にしています。特に、定数を返す組み込み関数呼び出しが、lencapの定数性判断においてどのように扱われるかについて、コンパイラ(gcgccgo)や型チェッカー(go/types)間の不整合を解消することを目的としています。

変更の背景

Go言語では、lencapといった組み込み関数は、引数が特定の条件を満たす場合に定数式として評価されることがあります。例えば、文字列定数のlenや、配列・配列へのポインタのlen/capは、その引数にチャネル受信や「関数呼び出し」が含まれない限り定数となります。

しかし、この「関数呼び出し」の解釈に関して、Goの異なるツールチェーン間で不整合が存在していました。

  • gccgo: 定数を返す組み込み関数呼び出し(例: imag(2i))を「関数呼び出し」とは見なさないため、len/capの引数に含まれても定数式として扱っていました(Issue 7386)。
  • go/types: 通常の関数呼び出しだけでなく、定数を返す組み込み関数呼び出しも含め、あらゆる呼び出しを「関数呼び出し」と見なしていました(Issue 7457)。
  • gc: この点に関して一貫性がありませんでした(Issue 7385)。

この不整合は、開発者がGoのコードを書く際に、lencapが定数として評価されるかどうかの予測を困難にし、移植性や挙動の一貫性に問題を引き起こす可能性がありました。このコミットは、仕様書の記述を修正し、この曖昧さを解消することで、すべてのツールチェーンが同じ解釈をするように導くことを目的としています。

前提知識の解説

Go言語の定数

Go言語における定数(Constants)は、コンパイル時に値が確定する不変のエンティティです。数値、真偽値、文字列の定数があります。定数式は、定数のみから構成される式で、コンパイル時に評価され、結果も定数となります。これにより、プログラムの実行前に値が決定されるため、パフォーマンスの向上や、コンパイル時のエラーチェックが可能になります。

lencap組み込み関数

lencapはGo言語に組み込まれた関数で、それぞれ以下の用途で使用されます。

  • len(v): vの長さ(要素数)を返します。vが文字列、配列、スライス、マップ、チャネルの場合に適用可能です。
  • cap(v): vの容量を返します。vが配列、スライス、チャネルの場合に適用可能です。

これらの関数は、引数の型や内容によっては定数式として評価されることがあります。例えば、len("hello")は文字列定数の長さなので、5という定数になります。また、len([10]int{})のように配列の長さも定数です。

定数式と非定数式

Goの仕様では、len(s)cap(s)が定数となる条件として、「sがチャネル受信や関数呼び出しを含まない場合」という制約があります。この「関数呼び出し」の解釈が、今回のコミットの焦点です。

  • 定数式: コンパイル時に評価され、結果が定数となる式。例: 1 + 2, len("abc")
  • 非定数式: 実行時に評価される式。例: x + yx, yが変数の場合)、f()(関数呼び出し)。

Goのコンパイラとツールチェーン

Go言語には主要なコンパイラがいくつか存在します。

  • gc: Goプロジェクトが公式に開発している標準のコンパイラ。
  • gccgo: GCC(GNU Compiler Collection)をバックエンドとして使用するGoコンパイラ。
  • go/types: Goの型システムを実装したライブラリで、Goのソースコードの型チェックやセマンティック解析に使用されます。IDEやリンター、その他の開発ツールで利用されます。

これらのツールがGoの仕様をどのように解釈し、実装するかが、Goプログラムの挙動に影響を与えます。特に、仕様の曖昧な点は、ツール間で異なる挙動を引き起こす原因となります。

技術的詳細

このコミットの核心は、Go言語の仕様書におけるlenおよびcap組み込み関数の定数性に関する記述の明確化です。具体的には、len(s)cap(s)が定数となる条件の一つである「sが関数呼び出しを含まない」という部分の「関数呼び出し」の定義を厳密にしています。

以前の仕様では、「関数呼び出し」という言葉が、コンパイル時に定数結果を返す組み込み関数(例: imag(2i))を含むのか、それとも実行時に評価される非定数な関数呼び出しのみを指すのかが曖昧でした。

この曖昧さにより、以下のような問題が発生していました。

  1. gccgoの挙動: gccgoは、imag(2i)のように定数を返す組み込み関数呼び出しを「関数呼び出し」とは見なさず、その結果がlencapの引数に含まれても、lencapを定数として扱っていました。これは、定数伝播(constant propagation)の最適化と関連しています。コンパイラがコンパイル時に式の値を計算できる場合、それを定数として扱うことで、実行時の計算を省略できます。
  2. go/typesの挙動: go/typesは、定数かどうかにかかわらず、すべての組み込み関数呼び出しを「関数呼び出し」と見なしていました。そのため、imag(2i)lencapの引数に含まれる場合、lencapを非定数として扱っていました。これは、より厳密な型チェックと仕様の解釈に基づいています。
  3. gcの挙動: gcコンパイラはこの点に関して一貫性がなく、場合によって挙動が異なっていました。

このコミットは、仕様書に「(非定数な)関数呼び出し」という表現を追加し、さらに具体的な例を示すことで、この問題を解決します。これにより、lencapの定数性判断において、「関数呼び出し」とは実行時に評価され、定数結果を返さない関数呼び出しのみを指すことが明確になります。定数結果を返す組み込み関数呼び出しは、その結果が定数であるため、lencapの定数性を妨げない、という解釈が統一されます。

この変更により、gccgoの既存の挙動が仕様に合致することになり、go/typesgcもこの新しい明確化された仕様に従うことが期待されます。結果として、Goプログラムのlencapの定数性に関する挙動が、ツールチェーン間で一貫するようになります。

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

変更はdoc/go_spec.htmlファイルに集中しており、lencapの組み込み関数に関するセクションが修正されています。

--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -1,6 +1,6 @@
 <!--{
  "Title": "The Go Programming Language Specification",
-"Subtitle": "Version of Feb 27, 2014",
+"Subtitle": "Version of March 4, 2014",
  "Path": "/ref/spec"
 }-->
 
@@ -5271,12 +5271,22 @@ The expression <code>len(s)</code> is <a href=\"#Constants\">constant</a> if
 <code>s</code> is a string constant. The expressions <code>len(s)</code> and
 <code>cap(s)</code> are constants if the type of <code>s</code> is an array
 or pointer to an array and the expression <code>s</code> does not contain
-<a href=\"#Receive_operator\">channel receives</a> or
+<a href=\"#Receive_operator\">channel receives</a> or (non-constant)
 <a href=\"#Calls\">function calls</a>; in this case <code>s</code> is not evaluated.\n Otherwise, invocations of <code>len</code> and <code>cap</code> are not\n constant and <code>s</code> is evaluated.\n </p>\n \n+<pre>\n+const (\n+\tc1 = imag(2i)                    // imag(2i) = 2.0 is a constant\n+\tc2 = len([10]float64{2})         // [10]float64{2} contains no function calls\n+\tc3 = len([10]float64{c1})        // [10]float64{c1} contains no function calls\n+\tc4 = len([10]float64{imag(2i)})  // imag(2i) is a constant and no function call is issued\n+\tc5 = len([10]float64{imag(z)})   // invalid: imag(x) is a (non-constant) function call\n+)\n+var z complex128\n+</pre>\n \n <h3 id=\"Allocation\">Allocation</h3>\n \n```

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

このコミットによる変更は、Go言語の仕様書における以下の2点です。

1.  **仕様書のバージョン日付の更新**:
    `"Subtitle": "Version of Feb 27, 2014"` が `"Subtitle": "Version of March 4, 2014"` に更新されています。これは、仕様の変更が反映された日付を示しています。

2.  **`len`/`cap`の定数性に関する記述の明確化**:
    `doc/go_spec.html`の`len`と`cap`の定数性に関する段落が修正されています。

    *   変更前: `... or <a href="#Calls">function calls</a>; ...`
    *   変更後: `... or (non-constant) <a href="#Calls">function calls</a>; ...`

    この変更により、「関数呼び出し」という言葉の前に「(非定数な)」という修飾が追加されました。これは、`len`や`cap`の引数に含まれる関数呼び出しが、その`len`や`cap`の定数性を妨げるのは、その関数呼び出しが**非定数**である場合のみであることを明確にしています。つまり、`imag(2i)`のようにコンパイル時に定数結果を返す組み込み関数呼び出しは、`len`や`cap`の定数性を妨げない、という解釈が導入されました。

    さらに、この変更を補強するために、具体的なコード例が追加されています。

    ```go
    const (
    	c1 = imag(2i)                    // imag(2i) = 2.0 is a constant
    	c2 = len([10]float64{2})         // [10]float64{2} contains no function calls
    	c3 = len([10]float64{c1})        // [10]float64{c1} contains no function calls
    	c4 = len([10]float64{imag(2i)})  // imag(2i) is a constant and no function call is issued
    	c5 = len([10]float64{imag(z)})   // invalid: imag(x) is a (non-constant) function call
    )
    var z complex128
    ```

    この例は、以下の点を具体的に示しています。

    *   `c1 = imag(2i)`: `imag(2i)`は定数`2.0`を返すため、`c1`は定数となる。
    *   `c2 = len([10]float64{2})`: 配列リテラル`[10]float64{2}`には関数呼び出しが含まれないため、`len`は定数となる。
    *   `c3 = len([10]float64{c1})`: 配列リテラル`[10]float64{c1}`には定数`c1`が含まれるが、関数呼び出しではないため、`len`は定数となる。
    *   `c4 = len([10]float64{imag(2i)})`: `imag(2i)`は定数`2.0`を返す組み込み関数呼び出しであり、これは「非定数な関数呼び出し」ではないため、`len`は定数となる。
    *   `c5 = len([10]float64{imag(z)})`: `imag(z)`は変数`z`を引数にとるため、実行時に評価される「非定数な関数呼び出し」となる。したがって、この`len`は定数とはならず、コンパイルエラー(`invalid`)となる。

これらの変更により、Goの仕様における`len`と`cap`の定数性に関するルールがより明確になり、異なるGoツールチェーン間での解釈の不整合が解消されることが期待されます。

## 関連リンク

*   Go言語の仕様書: [https://go.dev/ref/spec](https://go.dev/ref/spec)
*   Go issue #7387 (詳細不明)
*   Go issue #7386 (詳細不明、コミットメッセージによると`gccgo`の挙動に関するもの)
*   Go issue #7385 (詳細不明、コミットメッセージによると`gc`の一貫性に関するもの)
*   Go issue #7457 (詳細不明、コミットメッセージによると`go/types`の挙動に関するもの)

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

*   GitHubコミットページ: [https://github.com/golang/go/commit/871698136d7a6452267b90e06d7cab4fc1f7cfea](https://github.com/golang/go/commit/871698136d7a6452267b90e06d7cab4fc1f7cfea)
*   Gerrit Change-Id: `https://golang.org/cl/66860046` (Goプロジェクトのコードレビューシステム)