[インデックス 10673] ファイルの概要
このコミットは、Go言語のバージョン1(Go 1)における主要な言語仕様の変更点をまとめたドキュメントの更新と、それに関連するコード例の追加を目的としています。具体的には、doc/go1.html
とdoc/go1.tmpl
というGo 1のリリースノートまたはドキュメントのテンプレートファイルが更新され、これらの変更点を説明するGoのコード例がdoc/progs/go1.go
に追加されています。
コミット
commit 136c04f71adf3611d94c33552aebb63290647580
Author: Rob Pike <r@golang.org>
Date: Thu Dec 8 16:39:05 2011 -0800
doc/go1: most of the simple language changes
R=rsc, adg, r
CC=golang-dev
https://golang.org/cl/5477044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/136c04f71adf3611d94c33552aebb63290647580
元コミット内容
doc/go1: most of the simple language changes
R=rsc, adg, r
CC=golang-dev
https://golang.org/cl/5477044
変更の背景
Go言語は、その初期段階から継続的に進化を遂げてきました。Go 1は、言語の安定性と互換性を保証するための重要なマイルストーンとなるリリースでした。このコミットは、Go 1のリリースに向けて、言語仕様に加えられた「単純な」変更点の多くを公式ドキュメントに反映させることを目的としています。
変更の背景には、以下のような理由が考えられます。
- 言語仕様の明確化と安定化: Go 1では、将来にわたる互換性を保証するために、それまで曖昧だったり、実装依存だったりした挙動を明確に定義する必要がありました。例えば、マップのイテレーション順序の非決定性や、多重代入の評価順序の明確化などがこれに当たります。
- 既存のバグや非推奨の挙動の修正:
close
関数が受信専用チャネルに対して呼び出されることの禁止や、シャドウされた名前付き戻り値を持つ関数でのreturn
ステートメントの制限などは、潜在的なバグを防ぎ、より堅牢なコードを奨励するための変更です。 - 利便性の向上と新機能の導入:
append
関数での文字列からバイトスライスへの直接追加や、構造体・配列の等価性比較の定義、それによるマップキーとしての利用などは、開発者の利便性を高め、言語の表現力を向上させるための新機能です。 - 初期化処理の改善:
init
関数内でのゴルーチンの実行許可は、初期化ロジックの柔軟性を高め、デッドロックのリスクを低減することを目的としています。
これらの変更は、Go言語がより成熟し、実用的なプログラミング言語として広く採用されるための基盤を固める上で不可欠でした。
前提知識の解説
このコミットの変更内容を理解するためには、Go言語の以下の基本的な概念を理解している必要があります。
- スライス (Slice): Go言語の動的な配列。基になる配列の一部を参照します。
append
関数はスライスに要素を追加するために使用されます。 - チャネル (Channel): ゴルーチン間で値を送受信するための通信メカニズム。
close
関数はチャネルがこれ以上値を送信しないことを示すために使用されます。 - ゴルーチン (Goroutine): Goランタイムによって管理される軽量なスレッド。並行処理を実現するために使用されます。
init
関数: パッケージが初期化される際に自動的に実行される特殊な関数。各パッケージは複数のinit
関数を持つことができ、これらはパッケージ内のすべての変数が初期化された後に、宣言順に実行されます。- マップ (Map): キーと値のペアを格納するハッシュテーブル。Goでは、
for range
ループを使ってマップをイテレートできます。 - 多重代入 (Multiple Assignment): 複数の変数に一度に値を代入するGoの機能。例:
a, b = b, a
。 - シャドウイング (Shadowing): 内側のスコープで宣言された変数が、外側のスコープの同じ名前の変数を「隠す」こと。
- 名前付き戻り値 (Named Return Values): 関数の戻り値に変数を宣言し、関数内でその変数に値を代入することで、
return
ステートメントで明示的に値を指定せずに戻り値を返すことができる機能。 - 構造体 (Struct): 異なる型のフィールドをまとめた複合データ型。
- 配列 (Array): 同じ型の要素を固定長で格納するデータ構造。
- 等価性 (Equality): 2つの値が同じであるかどうかを比較する操作 (
==
演算子)。Goでは、特定の型に対して等価性が定義されています。 - 可変長引数 (Variadic Function): 引数の数が可変である関数。引数リストの最後に
...
を付けて宣言します。
技術的詳細
このコミットで文書化されているGo 1の言語変更点は多岐にわたります。以下にそれぞれの技術的詳細を解説します。
1. append
関数の変更
- 可変長引数としての振る舞い:
append
関数は元々可変長引数を受け入れるように設計されていましたが、Go 1ではこの性質がより明確に強調されました。これにより、バイトスライスに別のバイトスライスを結合する際に、...
演算子を使用して要素を展開する構文が推奨されます。greeting := []byte{} greeting = append(greeting, []byte("hello ")...) // バイトスライスを展開して追加
- 文字列からバイトスライスへの直接追加: Go 1では、
append
関数を使って文字列を直接バイトスライスに追加できるようになりました。これにより、以前は必要だった[]byte("string")
のような明示的な型変換が不要になり、コードが簡潔になります。
これは、greeting = append(greeting, "world"...) // 文字列を直接バイトスライスに追加
copy
関数が文字列からバイトスライスへのコピーを直接サポートしているのと同様の考え方に基づいています。
2. close
関数の変更
- 受信専用チャネルへの
close
の禁止: Go 1では、型システムがチャネルの方向性をより厳密に強制するようになりました。具体的には、受信専用チャネル (<-chan T
) に対してclose
関数を呼び出すことがコンパイルエラーとなります。これは、チャネルのclose
は送信側が行うべき操作であり、受信側がチャネルを閉じることは論理的に誤っているためです。
この変更は、既存の誤ったコードをコンパイル時に検出することを目的としています。var c chan int var csend chan<- int = c // 送信専用チャネル var crecv <-chan int = c // 受信専用チャネル close(c) // 合法 close(csend) // 合法 close(crecv) // 違法 (コンパイルエラー)
3. init
関数内でのゴルーチンの実行
- 初期化中のゴルーチン実行の許可: Go 1では、
init
関数内で作成されたゴルーチンが、init
関数の完了を待たずに直ちに実行されるようになりました。以前のバージョンでは、init
関数内で作成されたゴルーチンは、すべての初期化処理が完了するまで実行が開始されませんでした。 この変更により、init
ルーチンやグローバル初期化式からゴルーチンを使用するコードがデッドロックを引き起こすことなく呼び出せるようになり、初期化ロジックの柔軟性が向上しました。
この変更は新機能であり、既存のコードに影響を与える可能性は低いとされていますが、var PackageGlobal int func init() { c := make(chan int) go initializationFunction(c) // init関数内でゴルーチンが即座に実行される PackageGlobal = <-c // ゴルーチンからの値を受け取る } func initializationFunction(c chan int) { c <- 1 }
main
関数が開始されるまでゴルーチンが実行されないことに依存していたコードは影響を受ける可能性があります。
4. マップのイテレーション順序の非決定性
for range
によるマップイテレーションの順序: Go 1では、for range
ステートメントを使用してマップをイテレートする際の要素の訪問順序が「予測不可能」であると明確に定義されました。これは、同じループを同じマップに対して複数回実行した場合でも、順序が異なる可能性があることを意味します。
この変更は、マップの内部実装が変更された場合でも、コードが特定のイテレーション順序に依存しないようにするためのものです。以前のバージョンでも順序は保証されていませんでしたが、Go 1でその非決定性が明示的に規定されました。これにより、特定の順序に依存していた既存のコードは動作が不安定になる可能性があります。m := map[string]int{"Sunday": 0, "Monday": 1} for name, value := range m { // このループは、"Sunday"が最初に訪問されると仮定すべきではない。 f(name, value) }
5. 多重代入の評価順序の明確化
- 左から右への評価と代入: Go 1では、多重代入ステートメントにおける式の評価順序が完全に指定されました。具体的には、代入ステートメントの左辺に、関数呼び出しや配列のインデックス操作など、評価が必要な式が含まれる場合、これらはすべて通常の「左から右」の規則に従って評価されます。すべての式が評価された後、実際の代入が左から右の順序で実行されます。
この変更は、以前のバージョンで未指定だった挙動を明確にするものであり、ほとんどの既存コードには影響がないとされています。sa := []int{1, 2, 3} i := 0 i, sa[i] = 1, 2 // i = 1, sa[0] = 2 となる (iが評価されてからsa[i]が評価される) sb := []int{1, 2, 3} j := 0 sb[j], j = 2, 1 // sb[0] = 2, j = 1 となる (sb[j]が評価されてからjが評価される) sc := []int{1, 2, 3} sc[0], sc[0] = 1, 2 // sc[0] = 1, その後 sc[0] = 2 となる (最終的にsc[0] = 2)
6. 戻り値とシャドウされた変数
- シャドウされた名前付き戻り値を持つ関数での
return
の制限: Go 1のコンパイラは、名前付き戻り値を持つ関数において、return
ステートメントが引数なしで呼び出され、かつそのreturn
ステートメントの時点でいずれかの名前付き戻り値がシャドウされている場合、そのコードを拒否するようになりました。これは、シャドウイングによって意図しない戻り値が返される可能性を防ぐためのものです。
この変更は、コンパイラによって検出されるバグの修正を促すものであり、手動での修正が必要です。func Bug() (i, j, k int) { for i = 0; i < 5; i++ { for j := 0; j < 5; j++ { // jを再宣言 (シャドウイング) k += i*j if k > 100 { return // 拒否される: ここでjがシャドウされているため } } } return // OK: ここではjはシャドウされていない }
7. 構造体と配列の等価性
==
および!=
演算子の定義: Go 1では、構造体と配列の値に対して等価性 (==
) および非等価性 (!=
) が定義されました。これは、データ構造のすべての要素が比較可能である場合に限られます。つまり、構造体のすべてのフィールド(または配列のすべての要素)に対して等価性が定義されていれば、その構造体(または配列)全体に対しても等価性が定義されます。- マップキーとしての利用: この変更の結果、構造体と配列はマップのキーとして使用できるようになりました。これは、Goのマップキーが比較可能である必要があるためです。
ただし、スライスは依然として等価性が未定義であり、比較演算子 (type Day struct { long string short string } Christmas := Day{"Christmas", "XMas"} Thanksgiving := Day{"Thanksgiving", "Turkey"} holiday := map[Day]bool{ // 構造体をマップキーとして使用 Christmas: true, Thanksgiving: true, } fmt.Printf("Christmas is a holiday: %t\n", holiday[Christmas])
<
,<=
,>
,>=
) も構造体と配列に対しては未定義のままです。これは新機能であり、既存のコードに影響はありません。
8. 関数とマップの等価性
nil
との比較以外の等価性チェックの禁止: Go 1では、関数とマップの等価性をnil
と比較する場合を除いて、==
演算子で比較することが禁止されました。これは、関数やマップの等価性を定義することが一般的に困難であり、意図しない挙動を引き起こす可能性があるためです。
この変更は、コンパイルエラーを引き起こすため、手動での修正が必要です。影響を受けるプログラムは少ないとされていますが、修正には設計の見直しが必要になる場合があります。func myFunc() {} var f1 func() = myFunc var f2 func() = myFunc // if f1 == f2 { ... } // 違法 (コンパイルエラー) m1 := make(map[string]int) m2 := make(map[string]int) // if m1 == m2 { ... } // 違法 (コンパイルエラー) if f1 == nil { ... } // 合法 if m1 == nil { ... } // 合法
コアとなるコードの変更箇所
このコミットでは、主に以下の3つのファイルが変更されています。
doc/go1.html
: Go 1の公式ドキュメントの一部であり、言語変更点を説明するHTMLファイルです。このコミットでは、append
、close
、init
関数内のゴルーチン、マップのイテレーション、多重代入、シャドウされた戻り値、構造体と配列の等価性、関数とマップの等価性に関するセクションが追加または更新されています。doc/go1.tmpl
:doc/go1.html
のテンプレートファイルです。doc/go1.html
と同様に、上記の言語変更点に関する説明が追加されています。このファイルは、Goのドキュメント生成システムで使用されるテンプレートです。doc/progs/go1.go
: 上記の言語変更点を具体的に示すためのGoのコード例が含まれるファイルです。このコミットでは、stringAppend
、mapIteration
、multipleAssignment
、structEquality
などの新しい関数が追加され、それぞれの言語変更の挙動を実証するコードが記述されています。
コアとなるコードの解説
doc/progs/go1.go
は、doc/go1.html
およびdoc/go1.tmpl
で説明されている言語変更点を実際に動作させるためのサンプルコード集です。
stringAppend()
:append
関数が文字列を直接バイトスライスに追加できるようになったことを示します。func stringAppend() { greeting := []byte{} greeting = append(greeting, []byte("hello ")...) greeting = append(greeting, "world"...) // ここが新しい挙動 if string(greeting) != "hello world" { log.Fatal("stringAppend: ", string(greeting)) } }
mapIteration()
: マップのイテレーション順序が非決定性であることを示唆するコメントが含まれています。func mapIteration() { m := map[string]int{"Sunday": 0, "Monday": 1} for name, value := range m { // This loop should not assume Sunday will be visited first. f(name, value) } }
multipleAssignment()
: 多重代入の評価順序が左から右であることを示す具体的な例が含まれています。func multipleAssignment() { sa := []int{1, 2, 3} i := 0 i, sa[i] = 1, 2 // sets i = 1, sa[0] = 2 sb := []int{1, 2, 3} j := 0 sb[j], j = 2, 1 // sets sb[0] = 2, j = 1 sc := []int{1, 2, 3} sc[0], sc[0] = 1, 2 // sets sc[0] = 1, then sc[0] = 2 (so sc[0] = 2 at end) assert(i == 1 && sa[0] == 2) assert(j == 1 && sb[0] == 2) assert(sc[0] == 2) }
structEquality()
: 構造体の等価性比較と、それがマップキーとして使用できるようになったことを示すコメントアウトされた例が含まれています。func structEquality() { // Feature not net in repo. // type Day struct { // long string // short string // } // Christmas := Day{"Christmas", "XMas"} // Thanksgiving := Day{"Thanksgiving", "Turkey"} // holiday := map[Day]bool { // Christmas: true, // Thanksgiving: true, // } // fmt.Printf("Christmas is a holiday: %t\n", holiday[Christmas]) }
init()
関数とinitializationFunction()
:init
関数内でゴルーチンが実行されることを示す例です。func initializationFunction(c chan int) { c <- 1 } var PackageGlobal int func init() { c := make(chan int) go initializationFunction(c) PackageGlobal = <-c }
これらのコード例は、Go 1で導入された言語変更の挙動を開発者が実際に確認し、理解するための重要なリソースとなっています。
関連リンク
- Go 1 Release Notes (公式ドキュメント): このコミットが更新しているドキュメントの最終版は、Goの公式ウェブサイトで公開されています。
- https://go.dev/doc/go1 (Go 1のリリースノート)
参考にした情報源リンク
- https://github.com/golang/go/commit/136c04f71adf3611d94c33552aebb63290647580 (このコミットのGitHubページ)
- https://golang.org/cl/5477044 (Gerrit上の変更リスト)
- Go言語の公式ドキュメント (Go 1のリリースノートを含む)
- Go言語の仕様書
- Go言語に関する一般的なプログラミング知識