[インデックス 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.go
test/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
の挙動に関する一般的なプログラミング知識