Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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 は常に最も内側の forswitch、または select ステートメントからのみ抜け出します。

したがって、bug119.go のテストケースは、selectdefault ケースで break が実行された後、for ループの次のイテレーションに進むのではなく、for ループ自体を終了させると誤って想定していました。この誤解により、テストは select ステートメントの直後に配置された panic() 関数が実行されないことを期待していましたが、実際には breakselect からしか抜け出さないため、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 ループは、他の言語の forwhiledo-while ループの機能を兼ね備えています。

  • 無限ループ:
    for {
        // 無限に繰り返されるコード
    }
    
  • 条件付きループ:
    for condition {
        // condition が true の間繰り返されるコード
    }
    
  • 従来の for ループ:
    for init; condition; post {
        // init で初期化、condition が true の間繰り返し、post で更新
    }
    

break キーワードの挙動

break キーワードは、Go言語において forswitchselect ステートメントの実行を終了するために使用されます。

  • スコープ: break は、常に最も内側の forswitch、または select ステートメントから抜け出します。外側のループやステートメントには影響を与えません。
  • ラベル付き break: 特定のラベルを付けて break を使用することで、ネストされたループやステートメントの特定の外側のものから抜け出すことができます。しかし、これはこのコミットの文脈では関係ありません。

このコミットの核心は、breakselect ステートメントの内部で使用された場合、それが 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 が使用されている点にあります。

  1. a := new(chan bool);bool 型のチャネルへのポインタ a を作成していますが、これは実質的に nil チャネルです。nil チャネルからの受信 (<- a) は常にブロックします。
  2. for { ... } は無限ループです。
  3. select ステートメントに入ると、case <- a:nil チャネルからの受信を試みるため、常にブロックします。
  4. default: ケースが存在するため、select はブロックせずに即座に default ケースを実行します。
  5. default ケース内の break; は、最も内側の select ステートメントから抜け出します。これは for ループから抜け出すものではありません。
  6. break によって select ステートメントが終了した後、コードの実行は select ブロックの直後にある panic(); に移ります。
  7. したがって、このプログラムは常に 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)
  }
}

各行の解説:

  1. a := new(chan bool);: bool 型のチャネルへのポインタ a を宣言し、初期化しています。new はゼロ値を割り当てるため、これは nil チャネルへのポインタとなります。nil チャネルに対する送受信操作は常にブロックします。
  2. for {: 無限ループを開始します。このループは明示的な break または return、あるいは panic が発生しない限り、永遠に繰り返されます。
  3. select {: 複数のチャネル操作を待機するための select ステートメントを開始します。
  4. case <- a:: チャネル a から値を受信しようとします。前述の通り、anil チャネルなので、この case は常にブロックし、実行されることはありません。
  5. default:: select ステートメント内のどの case も準備ができていない場合に実行されるブロックです。この場合、case <- a が常にブロックするため、default が常に選択されます。
  6. break;: default ケース内で実行される break ステートメントです。Go言語の仕様により、この break最も内側の select ステートメントからのみ抜け出します。つまり、select { ... } ブロックの実行を終了させますが、外側の for { ... } ループの実行は継続させます。
  7. panic();: select ステートメントの直後に配置された panic 関数呼び出しです。default ケースの breakselect からしか抜け出さないため、for ループの各イテレーションで select が終了した後、必ずこの panic() が実行されます。

このテストケースは、breakselect から抜け出すだけでなく、外側の for ループからも抜け出すと誤って想定していました。そのため、panic() が実行されないことを期待していましたが、実際には panic() が常に実行されてしまい、テストの意図と実際の挙動が一致しませんでした。この不一致が「誤ったテストケース」と判断された理由です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (上記「関連リンク」に記載)
  • Go言語のソースコードリポジトリ (GitHub)
  • Go言語の select および break の挙動に関する一般的なプログラミング知識