[インデックス 1084] ファイルの概要
このコミットは、Go言語のテストスイートから誤ったテストケース test/bugs/bug119.go を削除するものです。このテストケースは、select ステートメント内の break の挙動に関する誤解に基づいており、本来パニックすべきではない状況でパニックを期待していました。
コミット
commit 9c7374d71b32bb3cd9d19dcdb556ddeed085f34c
Author: Ian Lance Taylor <iant@golang.org>
Date: Thu Nov 6 17:39:48 2008 -0800
Erroneous test case. The break statement should break out of
a select clause.
R=ken
DELTA=20 (0 added, 20 deleted, 0 changed)
OCL=18731
CL=18739
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9c7374d71b32bb3cd9d19dcdb556ddeed085f34c
元コミット内容
Erroneous test case. The break statement should break out of
a select clause.
変更の背景
このコミットの背景には、Go言語の select ステートメントと break キーワードの挙動に関する誤解がありました。削除された test/bugs/bug119.go は、select ステートメントの default ケース内で break が実行された際に、外側の for ループ全体から抜け出すことを期待していました。しかし、Go言語の仕様では、break は常に最も内側の for、switch、または select ステートメントからのみ抜け出します。
したがって、bug119.go のテストケースは、select の default ケースで break が実行された後、for ループの次のイテレーションに進むのではなく、for ループ自体を終了させると誤って想定していました。この誤解により、テストは select ステートメントの直後に配置された panic() 関数が実行されないことを期待していましたが、実際には break が select からしか抜け出さないため、panic() が常に実行されてしまい、テストが失敗していました。
このテストケースはGo言語の正しい挙動を反映していなかったため、「誤ったテストケース (Erroneous test case)」として削除されました。これは、Go言語の初期開発段階における言語仕様の理解とテストの正確性に関する調整の一環と考えられます。
前提知識の解説
Go言語の select ステートメント
select ステートメントは、Go言語における並行処理の強力な機能の一つです。複数の通信操作(チャネルの送受信)を待機し、準備ができた最初の操作を実行します。
- 構文:
select { case <-ch1: // ch1 から値を受信 case ch2 <- val: // ch2 に val を送信 default: // どのチャネル操作も準備ができていない場合に実行 } - 動作:
selectは、いずれかのcaseが準備できるまでブロックします。- 複数の
caseが準備できている場合、ランダムに1つが選択されます。 defaultケースが存在する場合、どのcaseも準備ができていないときにdefaultが即座に実行され、selectはブロックしません。defaultがない場合、selectはチャネル操作が準備できるまで無限にブロックします。
Go言語の for ループ
Go言語の for ループは、他の言語の for、while、do-while ループの機能を兼ね備えています。
- 無限ループ:
for { // 無限に繰り返されるコード } - 条件付きループ:
for condition { // condition が true の間繰り返されるコード } - 従来の for ループ:
for init; condition; post { // init で初期化、condition が true の間繰り返し、post で更新 }
break キーワードの挙動
break キーワードは、Go言語において for、switch、select ステートメントの実行を終了するために使用されます。
- スコープ:
breakは、常に最も内側のfor、switch、またはselectステートメントから抜け出します。外側のループやステートメントには影響を与えません。 - ラベル付き
break: 特定のラベルを付けてbreakを使用することで、ネストされたループやステートメントの特定の外側のものから抜け出すことができます。しかし、これはこのコミットの文脈では関係ありません。
このコミットの核心は、break が select ステートメントの内部で使用された場合、それが select ステートメント自体を終了させるだけであり、select を囲む for ループには影響を与えないという点にあります。
技術的詳細
削除された test/bugs/bug119.go のコードは以下の通りでした。
package main
func main() {
a := new(chan bool); // チャネルのポインタを初期化 (実際には nil チャネル)
for { // 無限ループ
select {
case <- a: // nil チャネルからの受信は常にブロック
panic();
default: // 常に実行される
break; // select ステートメントから抜け出す
}
panic(); // ここが実行されることをテストは期待していなかった
}
}
このコードの技術的な問題点は、select ステートメント内の default ケースで break が使用されている点にあります。
a := new(chan bool);でbool型のチャネルへのポインタaを作成していますが、これは実質的にnilチャネルです。nilチャネルからの受信 (<- a) は常にブロックします。for { ... }は無限ループです。selectステートメントに入ると、case <- a:はnilチャネルからの受信を試みるため、常にブロックします。default:ケースが存在するため、selectはブロックせずに即座にdefaultケースを実行します。defaultケース内のbreak;は、最も内側のselectステートメントから抜け出します。これはforループから抜け出すものではありません。breakによってselectステートメントが終了した後、コードの実行はselectブロックの直後にあるpanic();に移ります。- したがって、このプログラムは常に
panic()を呼び出し、終了します。
元のテストケースは、「BUG: should not fail」というコメントがあり、panic が発生しないことを期待していました。しかし、Go言語の break の正しい挙動を理解すると、panic は避けられないことがわかります。テストケースがGo言語の正しいセマンティクスを反映していなかったため、誤りであると判断され、削除されました。
このコミットは、Go言語のコンパイラやランタイムのバグを修正するものではなく、テストケース自体の論理的な誤りを修正するものです。Go言語の設計思想として、キーワードの挙動は明確かつ一貫しているべきであり、テストケースもその仕様に厳密に従う必要があります。
コアとなるコードの変更箇所
このコミットでは、既存のコードの変更ではなく、誤ったテストケースの削除が行われています。
削除されたファイル:
test/bugs/bug119.gotest/golden.outからbug119.goに関連するテスト結果の記述
test/bugs/bug119.go の内容は以下の通りでした。
--- a/test/bugs/bug119.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// $G $D/$F.go && $L $F.$A && ./$A.out || echo BUG: should not fail
-
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-func main() {
- a := new(chan bool);
- for {
- select {
- case <- a:
- panic();
- default:
- break;
- }
- panic();
- }
-}
test/golden.out から削除された行は以下の通りです。
--- a/test/golden.out
+++ b/test/golden.out
@@ -153,10 +153,6 @@ BUG: should compile
=========== bugs/bug118.go
BUG should compile
-=========== bugs/bug119.go
-
-panic on line 82 PC=xxx
-BUG should not panic
=========== fixedbugs/bug016.go
fixedbugs/bug016.go:7: overflow converting constant to uint
コアとなるコードの解説
削除された test/bugs/bug119.go のコードは、Go言語の select ステートメントと break キーワードの挙動をテストしようとしたものでした。
package main
func main() {
a := new(chan bool); // (1)
for { // (2)
select { // (3)
case <- a: // (4)
panic();
default: // (5)
break; // (6)
}
panic(); // (7)
}
}
各行の解説:
a := new(chan bool);:bool型のチャネルへのポインタaを宣言し、初期化しています。newはゼロ値を割り当てるため、これはnilチャネルへのポインタとなります。nilチャネルに対する送受信操作は常にブロックします。for {: 無限ループを開始します。このループは明示的なbreakまたはreturn、あるいはpanicが発生しない限り、永遠に繰り返されます。select {: 複数のチャネル操作を待機するためのselectステートメントを開始します。case <- a:: チャネルaから値を受信しようとします。前述の通り、aはnilチャネルなので、このcaseは常にブロックし、実行されることはありません。default::selectステートメント内のどのcaseも準備ができていない場合に実行されるブロックです。この場合、case <- aが常にブロックするため、defaultが常に選択されます。break;:defaultケース内で実行されるbreakステートメントです。Go言語の仕様により、このbreakは最も内側のselectステートメントからのみ抜け出します。つまり、select { ... }ブロックの実行を終了させますが、外側のfor { ... }ループの実行は継続させます。panic();:selectステートメントの直後に配置されたpanic関数呼び出しです。defaultケースのbreakがselectからしか抜け出さないため、forループの各イテレーションでselectが終了した後、必ずこのpanic()が実行されます。
このテストケースは、break が select から抜け出すだけでなく、外側の for ループからも抜け出すと誤って想定していました。そのため、panic() が実行されないことを期待していましたが、実際には panic() が常に実行されてしまい、テストの意図と実際の挙動が一致しませんでした。この不一致が「誤ったテストケース」と判断された理由です。
関連リンク
- Go言語の
selectステートメントに関する公式ドキュメント (Go言語のバージョンによって内容が異なる可能性がありますが、基本的な挙動は共通です):- https://go.dev/tour/concurrency/5 (Go Tour - Select)
- https://go.dev/ref/spec#Select_statements (Go Language Specification - Select statements)
- Go言語の
breakステートメントに関する公式ドキュメント:- https://go.dev/ref/spec#Break_statements (Go Language Specification - Break statements)
参考にした情報源リンク
- Go言語の公式ドキュメント (上記「関連リンク」に記載)
- Go言語のソースコードリポジトリ (GitHub)
- Go言語の
selectおよびbreakの挙動に関する一般的なプログラミング知識