[インデックス 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
組み込み関数が定数式となる条件における「関数呼び出し」の定義を明確にしています。特に、定数を返す組み込み関数呼び出しが、len
やcap
の定数性判断においてどのように扱われるかについて、コンパイラ(gc
、gccgo
)や型チェッカー(go/types
)間の不整合を解消することを目的としています。
変更の背景
Go言語では、len
やcap
といった組み込み関数は、引数が特定の条件を満たす場合に定数式として評価されることがあります。例えば、文字列定数のlen
や、配列・配列へのポインタのlen
/cap
は、その引数にチャネル受信や「関数呼び出し」が含まれない限り定数となります。
しかし、この「関数呼び出し」の解釈に関して、Goの異なるツールチェーン間で不整合が存在していました。
gccgo
: 定数を返す組み込み関数呼び出し(例:imag(2i)
)を「関数呼び出し」とは見なさないため、len
/cap
の引数に含まれても定数式として扱っていました(Issue 7386)。go/types
: 通常の関数呼び出しだけでなく、定数を返す組み込み関数呼び出しも含め、あらゆる呼び出しを「関数呼び出し」と見なしていました(Issue 7457)。gc
: この点に関して一貫性がありませんでした(Issue 7385)。
この不整合は、開発者がGoのコードを書く際に、len
やcap
が定数として評価されるかどうかの予測を困難にし、移植性や挙動の一貫性に問題を引き起こす可能性がありました。このコミットは、仕様書の記述を修正し、この曖昧さを解消することで、すべてのツールチェーンが同じ解釈をするように導くことを目的としています。
前提知識の解説
Go言語の定数
Go言語における定数(Constants)は、コンパイル時に値が確定する不変のエンティティです。数値、真偽値、文字列の定数があります。定数式は、定数のみから構成される式で、コンパイル時に評価され、結果も定数となります。これにより、プログラムの実行前に値が決定されるため、パフォーマンスの向上や、コンパイル時のエラーチェックが可能になります。
len
とcap
組み込み関数
len
とcap
は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 + y
(x
,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)
)を含むのか、それとも実行時に評価される非定数な関数呼び出しのみを指すのかが曖昧でした。
この曖昧さにより、以下のような問題が発生していました。
gccgo
の挙動:gccgo
は、imag(2i)
のように定数を返す組み込み関数呼び出しを「関数呼び出し」とは見なさず、その結果がlen
やcap
の引数に含まれても、len
やcap
を定数として扱っていました。これは、定数伝播(constant propagation)の最適化と関連しています。コンパイラがコンパイル時に式の値を計算できる場合、それを定数として扱うことで、実行時の計算を省略できます。go/types
の挙動:go/types
は、定数かどうかにかかわらず、すべての組み込み関数呼び出しを「関数呼び出し」と見なしていました。そのため、imag(2i)
がlen
やcap
の引数に含まれる場合、len
やcap
を非定数として扱っていました。これは、より厳密な型チェックと仕様の解釈に基づいています。gc
の挙動:gc
コンパイラはこの点に関して一貫性がなく、場合によって挙動が異なっていました。
このコミットは、仕様書に「(非定数な)関数呼び出し」という表現を追加し、さらに具体的な例を示すことで、この問題を解決します。これにより、len
やcap
の定数性判断において、「関数呼び出し」とは実行時に評価され、定数結果を返さない関数呼び出しのみを指すことが明確になります。定数結果を返す組み込み関数呼び出しは、その結果が定数であるため、len
やcap
の定数性を妨げない、という解釈が統一されます。
この変更により、gccgo
の既存の挙動が仕様に合致することになり、go/types
やgc
もこの新しい明確化された仕様に従うことが期待されます。結果として、Goプログラムのlen
やcap
の定数性に関する挙動が、ツールチェーン間で一貫するようになります。
コアとなるコードの変更箇所
変更はdoc/go_spec.html
ファイルに集中しており、len
とcap
の組み込み関数に関するセクションが修正されています。
--- 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プロジェクトのコードレビューシステム)