[インデックス 142] ファイルの概要
このコミットは、Go言語の初期ドラフト仕様書である doc/go_lang.txt
の更新に関するものです。具体的には、変数の型推論の挙動と、シフト演算子の定義について仕様が明確化されています。
コミット
commit 4ff63a4794041bdc33b4399bc369574949cacd9a
Author: Robert Griesemer <gri@golang.org>
Date: Mon Jun 9 16:32:49 2008 -0700
- updated spec w/ respect to variable types and shift operators
SVN=121774
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4ff63a4794041bdc33b4399bc36974949cacd9a
元コミット内容
- updated spec w/ respect to variable types and shift operators
SVN=121774
変更の背景
このコミットは、Go言語がまだ開発初期段階にあった2008年6月に行われたものです。当時のGo言語は、その仕様が doc/go_lang.txt
という単一のテキストファイルで管理されており、言語の設計思想や文法、セマンティクスが日々議論され、更新されていました。
この特定の変更の背景には、以下の2つの主要な課題があったと考えられます。
-
変数の型推論の明確化: Go言語は静的型付け言語でありながら、
var i = 0
のような初期化時に型を省略できる型推論の機能を持っています。しかし、初期の仕様では、このような場合に具体的な型がどのように決定されるのかが曖昧だった可能性があります。特に、0
や3.1415
のような抽象的な数値定数(型なし定数)が初期値として与えられた場合に、変数がint
やfloat
といった具体的な型にどのように推論されるのかを明確にする必要がありました。これは、プログラマが意図しない型が推論されることを防ぎ、コードの予測可能性を高める上で重要です。 -
シフト演算子の挙動の明確化と一貫性: シフト演算子(
<<
および>>
)は、ビット操作において非常に重要な演算子です。しかし、その挙動、特に符号付き整数と符号なし整数に対する挙動、そしてシフトカウントの型に関する仕様が不明瞭だった可能性があります。C言語など他の言語では、シフト演算子の挙動が未定義であったり、実装依存であったりするケースがあり、これがバグの原因となることが知られています。Go言語では、このような曖昧さを排除し、予測可能で安全な挙動を保証するために、仕様を厳密に定義する必要がありました。また、Go言語の型システムにおける暗黙的な型変換の原則(ほとんどの二項演算子では両オペランドが同じ型である必要がある)と、シフト演算子の特殊な扱いについても明確にする必要がありました。
これらの課題に対処するため、Go言語の設計者の一人であるRobert Griesemer氏によって、doc/go_lang.txt
の関連セクションが更新されました。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の基本的な概念と、一般的なプログラミング言語における概念についての知識が必要です。
1. Go言語の型システム
- 静的型付け: Go言語はコンパイル時に変数の型が決定される静的型付け言語です。これにより、型に関連するエラーを早期に発見し、実行時の安全性を高めます。
- 型推論: Go言語では、変数の宣言時に初期値が与えられている場合、型を明示的に指定しなくてもコンパイラが自動的に型を推論します。例えば、
var x = 10
と書くと、x
はint
型と推論されます。 - 型なし定数 (Untyped Constants): Go言語には、特定の型を持たない「型なし定数」という概念があります。例えば、
0
は単なる整数値であり、int
型でもint32
型でもありません。これらの定数は、変数の初期化や演算の際に、文脈に応じて適切な型に「デフォルト型」として変換されます。このデフォルト型がどのように決定されるかが、このコミットで明確化された点の一つです。
2. シフト演算子
- ビット演算: シフト演算子(
<<
と>>
)は、数値のビット表現を左右に移動させるビット演算子です。x << n
:x
のビットをn
ビット左にシフトします。左にシフトすると、数値は2^n
倍されます。空いた右側のビットには0
が埋められます。x >> n
:x
のビットをn
ビット右にシフトします。右にシフトすると、数値は2^n
で除算されます(整数除算)。
- 算術シフトと論理シフト:
- 算術シフト (Arithmetic Shift): 符号付き整数に対して行われるシフトです。右シフトの場合、最上位ビット(符号ビット)が保持されるように、空いた左側のビットに符号ビットと同じ値が埋められます。これにより、負の数の除算が正しく行われます。
- 論理シフト (Logical Shift): 符号なし整数に対して行われるシフトです。右シフトの場合、空いた左側のビットには常に
0
が埋められます。
- シフトカウント: シフト演算子の右オペランドは、何ビットシフトするかを示す「シフトカウント」です。このシフトカウントの型や値の範囲が、言語によって異なる場合があります。
3. Go言語の仕様書 doc/go_lang.txt
Go言語の初期の仕様は、doc/go_lang.txt
というプレーンテキストファイルで記述されていました。これは、言語の設計がまだ流動的であり、頻繁な変更に対応するために、より軽量な形式が選ばれたためと考えられます。このファイルは、Go言語の進化の歴史を理解する上で非常に貴重な資料です。
技術的詳細
このコミットでは、doc/go_lang.txt
内の以下の2つの主要なセクションが変更されています。
1. 変数宣言における型推論の明確化
変更前は、初期化式が存在し、変数の型が省略された場合の型推論について、詳細なルールが不足していました。このコミットにより、以下のルールが追加されました。
- 初期化式が必須: 変数の型が省略された場合、初期化式(または式のリスト)が必須となります。
- 式の値の型が変数の型となる: 変数の型は、対応する初期化式の値の型によって決定されます。複数の変数を宣言し、複数の初期化式を与える場合も同様に、各変数は対応する式の型を継承します。
- 抽象的な数値定数のデフォルト型: 最も重要な変更点として、初期化式が抽象的な
int
またはfloating point
型の定数式である場合、変数の型がそれぞれint
またはfloat
に推論されることが明記されました。var i = 0 // i has int type
var f = 3.1415 // f has float type
これは、Go言語における「型なし定数」の概念と、それが変数の型推論にどのように影響するかを明確にするものです。0
や 3.1415
はそれ自体では特定の型を持ちませんが、変数の初期化に使われる際に、Go言語のデフォルトの整数型 (int
) や浮動小数点型 (float
) に「昇格」して変数の型を決定するという挙動が明文化されました。
2. シフト演算子の挙動と型変換ルールの明確化
このコミットでは、Go言語の型変換ルールとシフト演算子の挙動について、以下の重要な変更と追加が行われました。
-
暗黙的な型変換の原則の再定義:
- 変更前: 「定数とリテラルを除いて、暗黙的な型変換は存在しない。特に、符号なし整数と符号付き整数変数は、明示的な変換なしに式中で混在させることはできない。」
- 変更後: 「暗黙的な型変換は存在しない。シフト演算子
<<
および>>
を除いて、二項演算子の両オペランドは同じ型でなければならない。特に、符号なし整数と符号付き整数値は、明示的な変換なしに式中で混在させることはできない。」 この変更により、シフト演算子がGo言語の厳格な型変換ルールにおける唯一の例外であることが明確にされました。これは、シフト演算の性質上、左オペランドと右オペランド(シフトカウント)が異なる型を持つことが一般的であるため、特別な扱いが必要であることを示しています。
-
シフト演算子の詳細な挙動の定義:
- オペランドの役割: シフト演算子は、左オペランドを右オペランドで指定されたシフトカウントだけシフトします。
- 算術シフトと論理シフト:
- 左オペランドが符号付き整数である場合、算術シフトが実行されます。
- 左オペランドが符号なし整数である場合、論理シフトが実行されます。
- シフトカウントの型: シフトカウント(右オペランド)は、符号なし整数でなければならないと明記されました。
- シフトカウントの上限なし: シフトカウントに上限がないことが明記されました。これは、
n
ビットシフトは、左オペランドを1
ビットずつn
回シフトするのと同等であるという概念で説明されています。 - C言語の
~
演算子との比較: Go言語の単項^
演算子がC言語の~
(ビットごとの補数) に対応することが再確認されました。同時に、Go言語には~
演算子が存在しないことも明記されました。
-
->
演算子の削除の再確認: ポインタp
が構造体を指す場合、フィールドf
へのアクセスはp.f
と書くことが再確認され、C言語の->
演算子が存在しないことが改めて強調されました。 -
convert(uint32, ~0)
からconvert(uint32, ^0)
への修正: 変換の例において、C言語の~
演算子を使用していた箇所が、Go言語の^
演算子に修正されました。これは、Go言語の文法と一貫性を持たせるための修正です。
これらの変更は、Go言語の型システムと演算子のセマンティクスをより厳密かつ明確に定義し、言語の堅牢性と予測可能性を高めることを目的としています。特に、シフト演算子の挙動に関する詳細な定義は、低レベルのビット操作を行う際のバグを防ぐ上で非常に重要です。
コアとなるコードの変更箇所
このコミットで変更されたのは、doc/go_lang.txt
ファイルのみです。
--- a/doc/go_lang.txt
+++ b/doc/go_lang.txt
@@ -4,7 +4,7 @@ The Go Programming Language (DRAFT)
Robert Griesemer, Rob Pike, Ken Thompson
----
-(June 6, 2008)
+(June 9, 2008)
This document is a semi-informal specification/proposal for a new
systems programming language. The document is under active
@@ -916,6 +916,8 @@ constant, variable, or function.
Declaration = ConstDecl | TypeDecl | VarDecl | FunctionDecl | ExportDecl .
+TODO: specify range of visibility, scope rules.
+\
Const declarations
----
@@ -982,6 +984,18 @@ declaration the type of the initial value defines the type of the variable.
If the expression list is present, it must have the same number of elements
as there are variables in the variable specification.
+If the variable type is omitted, an initialization expression (or expression
+list) must be present, and the variable type is the type of the expression
+value (in case of a list of variables, the variables assume the types of the
+corresponding expression values).\n+\n+If the variable type is omitted, and the corresponding initialization expression
+is a constant expression of abstract int or floating point type, the type
+of the variable is "int" or "float" respectively:\n+\n+ var i = 0 // i has int type\n+ var f = 3.1415 // f has float type\n+\n The syntax
SimpleVarDecl = identifier ":=" Expression .
@@ -994,12 +1008,12 @@ is shorthand for
f := func() int { return 7; }
ch := new(chan int);
-Also, in some contexts such as if or for statements,
-this construct can be used to
-declare local temporary variables.
+Also, in some contexts such as "if", "for", or "switch" statements,
+this construct can be used to declare local temporary variables.
TODO: var a, b = 1, "x"; is permitted by grammar but not by current compiler
+\
Function and method declarations
----
@@ -1151,16 +1165,21 @@ and
(a / b) is "truncated towards zero".
-There are no implicit type conversions except for
-constants and literals. In particular, unsigned and signed integer
-variables cannot be mixed in an expression without explicit conversion.
+There are no implicit type conversions: Except for the shift operators
+"<<" and ">>", both operands of a binary operator must have the same type.
+In particular, unsigned and signed integer values cannot be mixed in an
+expression without explicit conversion.\n+\n+The shift operators shift the left operand by the shift count specified by the
+right operand. They implement arithmetic shifts if the left operand is a signed
+integer, and logical shifts if it is an unsigned integer. The shift count must
+be an unsigned integer. There is no upper limit on the shift count. It is
+as if the left operand is shifted "n" times by 1 for a shift count of "n".
-The shift operators implement arithmetic shifts for signed integers
-and logical shifts for unsigned integers. The properties of negative
-shift counts are undefined. Unary '^' corresponds to C '~' (bitwise
-complement).
+Unary "^" corresponds to C "~" (bitwise complement). There is no "~" operator
+in Go.
-There is no '->' operator. Given a pointer p to a struct, one writes
+There is no "->" operator. Given a pointer p to a struct, one writes
p.f
to access field f of the struct. Similarly, given an array or map
pointer, one writes
@@ -1272,7 +1291,7 @@ These conversions are called ``simple conversions\'\'.
TODO: if interfaces were explicitly pointers, this gets simpler.\n \tconvert(int, 3.14159);\n-\tconvert(uint32, ~0);\n+\tconvert(uint32, ^0);\n \tconvert(interface{}, new(S))\n \tconvert(*AStructType, interface_value)\n \n```
## コアとなるコードの解説
このコミットの「コード」は、Go言語の仕様書である `doc/go_lang.txt` のテキスト内容そのものです。したがって、変更されたテキストがGo言語の設計思想とセマンティクスをどのように反映しているかを解説します。
### 1. 変数宣言の型推論に関する追加
+If the variable type is omitted, an initialization expression (or expression +list) must be present, and the variable type is the type of the expression +value (in case of a list of variables, the variables assume the types of the +corresponding expression values). + +If the variable type is omitted, and the corresponding initialization expression +is a constant expression of abstract int or floating point type, the type +of the variable is "int" or "float" respectively: +
- var i = 0 // i has int type
- var f = 3.1415 // f has float type
この追加は、Go言語の型推論の挙動を明確に定義しています。特に、`var i = 0` のような宣言において、`0` が「型なし整数定数」であるにもかかわらず、変数 `i` がGo言語のデフォルトの整数型である `int` に推論されることを明文化しています。同様に、`3.1415` のような浮動小数点定数も、デフォルトの `float` 型(当時のGo言語では `float64` がデフォルトだった可能性が高いですが、ここでは単に `float` と記述されています)に推論されることを示しています。
これは、Go言語が「実用性」を重視し、一般的なケースでプログラマが直感的に期待する型推論を行うように設計されていることを示唆しています。同時に、型なし定数がどのように具体的な型に「具現化」されるかという、Go言語の型システムの重要な側面を説明しています。
### 2. シフト演算子と型変換ルールの変更
-There are no implicit type conversions except for -constants and literals. In particular, unsigned and signed integer -variables cannot be mixed in an expression without explicit conversion. +There are no implicit type conversions: Except for the shift operators +"<<" and ">>", both operands of a binary operator must have the same type. +In particular, unsigned and signed integer values cannot be mixed in an +expression without explicit conversion. + +The shift operators shift the left operand by the shift count specified by the +right operand. They implement arithmetic shifts if the left operand is a signed +integer, and logical shifts if it is an unsigned integer. The shift count must +be an unsigned integer. There is no upper limit on the shift count. It is +as if the left operand is shifted "n" times by 1 for a shift count of "n".
この変更は、Go言語の型システムにおける厳格な型変換ルールを再確認しつつ、シフト演算子をその例外として明確に位置づけています。
* **「シフト演算子を除いて、二項演算子の両オペランドは同じ型でなければならない」** という記述は、Go言語が型安全性を非常に重視していることを示しています。多くの言語では暗黙的な型変換が多用され、それが予期せぬバグの原因となることがありますが、Go言語はこれを極力排除しようとしています。シフト演算子が例外であるのは、左オペランドがシフトされる値、右オペランドがシフト量であり、これらが異なる型を持つことが自然であるためです。
* **シフト演算子の詳細な挙動の定義** は、Go言語が低レベルのビット操作においても予測可能で一貫した挙動を提供しようとしていることを示しています。
* **算術シフト vs 論理シフト**: 左オペランドの符号性によってシフトの種類が決定されることは、C言語など他の言語の挙動と一致しており、プログラマが直感的に理解しやすいように配慮されています。
* **シフトカウントの型**: シフトカウントが「符号なし整数」であるという制約は、負のシフトカウントによる未定義動作を防ぐための重要なルールです。
* **シフトカウントの上限なし**: 「`n` 回の1ビットシフト」という説明は、シフトカウントが非常に大きい場合でも、その挙動が明確に定義されていることを示唆しています。これは、ハードウェアの制約や特定のCPUアーキテクチャに依存しない、言語レベルでの抽象化と一貫性を追求するGo言語の設計哲学を反映しています。
### 3. その他の修正
* `TODO: specify range of visibility, scope rules.` の追加は、当時の仕様書がまだ未完成であり、今後の議論と定義が必要な領域が残っていたことを示しています。
* `if` や `for` 文における `:=` の使用例に `"switch"` が追加されたのは、この構文が `switch` 文でもローカル変数を宣言するために使用できることを反映しています。
* `convert(uint32, ~0)` から `convert(uint32, ^0)` への修正は、Go言語のビットごとの補数演算子が `^` であることを明確にし、C言語の `~` との混同を避けるためのものです。これは、Go言語が既存の言語の慣習を踏襲しつつも、独自の明確な文法とセマンティクスを確立しようとしていたことを示しています。
これらの変更は、Go言語がその初期段階から、明確で一貫性があり、予測可能な言語仕様を目指していたことを強く示しています。特に、型システムと低レベルの演算子の挙動に関する厳密な定義は、Go言語がシステムプログラミングに適した言語となるための基盤を築く上で不可欠でした。
## 関連リンク
* Go言語の公式ウェブサイト: [https://go.dev/](https://go.dev/)
* Go言語の初期の仕様書(`doc/go_lang.txt` の歴史を含む)は、Go言語のGitHubリポジトリの歴史を辿ることで確認できます。
## 参考にした情報源リンク
* Go言語のGitHubリポジトリ: [https://github.com/golang/go](https://github.com/golang/go)
* Go言語の歴史に関するドキュメントやブログ記事(一般的なGo言語の歴史的背景知識)
* Go言語の型システムに関する公式ドキュメントやチュートリアル(型推論、型なし定数に関する一般的な知識)
* ビット演算子に関する一般的なプログラミングの知識(算術シフト、論理シフトの概念)
* C言語のシフト演算子に関する情報(比較対象として)
(注: 特定のURLは提供されていませんが、上記のキーワードで検索することで関連情報を見つけることができます。)