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

[インデックス 1842] ファイルの概要

このコミットは、Go言語のtest/typeswitch.goという新しいテストファイルを追加するものです。このファイルは、Go言語における型スイッチ(type switch)の様々な挙動と機能を検証するために作成されました。具体的には、interface{}型の値が実行時にどのような具体的な型を持つかを判別し、それに応じた処理を行うswitch文のテストケースが含まれています。Go言語の初期段階において、型システムの中核をなす型スイッチの正確な動作を保証するための重要な追加です。

コミット

commit bcb464d221d677e62e365caad34ccb0a268f0754
Author: Rob Pike <r@golang.org>
Date:   Tue Mar 17 20:55:42 2009 -0700

    add test for type switches
    
    R=rsc
    DELTA=169  (169 added, 0 deleted, 0 changed)
    OCL=26433
    CL=26437

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/bcb464d221d677e62e365caad34ccb0a268f0754

元コミット内容

add test for type switches
    
R=rsc
DELTA=169  (169 added, 0 deleted, 0 changed)
OCL=26433
CL=26437

変更の背景

このコミットは2009年3月に行われており、Go言語がまだ活発に開発されていた初期段階に当たります。Go言語は静的型付け言語でありながら、動的な型チェックのメカニズムとして型スイッチを導入しました。interface{}型(空インターフェース)は、あらゆる型の値を保持できるため、Goにおけるポリモーフィズムの基盤となります。しかし、interface{}型の値が実際にどのような具体的な型を持っているかをプログラムが知るためには、実行時の型アサーションや型スイッチが必要になります。

この時期に型スイッチのテストが追加された背景には、以下の点が考えられます。

  1. 機能の安定化と検証: 型スイッチはGo言語の重要な機能の一つであり、その動作が仕様通りであることを厳密に検証する必要がありました。特に、様々な組み込み型(bool, int, float, stringなど)や複合型(struct, chan, array, map, funcなど)に対して正しく機能するかを確認することは不可欠です。
  2. コンパイラ/ランタイムの実装検証: 型スイッチの背後には、コンパイラが生成するコードとGoランタイムの型情報処理が密接に関わっています。このテストは、それらの実装が正しく行われているかを検証する役割も果たします。
  3. バグの早期発見: 新しい言語機能の実装には、予期せぬバグがつきものです。包括的なテストケースを追加することで、開発の早い段階で潜在的な問題を特定し、修正することができます。
  4. 言語仕様の明確化: テストケースは、言語の挙動を具体的なコードで示すため、言語仕様の非公式なドキュメントとしても機能します。

Rob Pike氏によるこのコミットは、Go言語の型システムが堅牢であることを保証するための、基礎的かつ重要な一歩であったと言えます。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念について理解しておく必要があります。

1. インターフェース (Interfaces)

Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは、JavaやC#のような明示的なimplementsキーワードを必要とせず、型がインターフェースで定義されたすべてのメソッドを実装していれば、そのインターフェースを満たすと見なされます(構造的型付け)。

  • 空インターフェース (interface{}): メソッドを一つも持たないインターフェースです。Goのすべての型は、少なくとも0個のメソッドを実装しているため、interface{}型はGoのあらゆる型の値を保持できます。これは、異なる型の値を統一的に扱いたい場合に非常に便利ですが、その値の具体的な型は実行時まで分かりません。

2. 型アサーション (Type Assertions)

型アサーションは、インターフェース型の値が、特定の具体的な型を持っているかどうかをチェックし、もし持っていればその具体的な型の値を取り出すための構文です。

var i interface{} = "hello"
s, ok := i.(string) // sは"hello", okはtrue
n, ok := i.(int)    // nは0, okはfalse

型アサーションは、インターフェースの値を具体的な型に「ダウンキャスト」するようなものですが、Goでは安全に実行時の型チェックが行われます。

3. switch文 (Switch Statements)

Goのswitch文は、他の言語のswitch文と似ていますが、より柔軟です。

  • 式スイッチ (Expression Switch): 特定の式の値に基づいてケースを評価します。
    x := 10
    switch x {
    case 5:
        // ...
    case 10:
        // ...
    default:
        // ...
    }
    
  • タグなしスイッチ (Untagged Switch): switchキーワードの後に式がない場合、各caseはブール式として評価されます。最初にtrueと評価されたcaseが実行されます。これは一連のif-else if-else文の糖衣構文と考えることができます。
    x := 10
    switch {
    case x < 0:
        // ...
    case x == 10:
        // ...
    default:
        // ...
    }
    

4. 型スイッチ (Type Switch)

型スイッチは、インターフェース型の値の動的な型に基づいて処理を分岐させる特殊なswitch文です。これは、switch文の式部分に.(type)という特別な構文を使用することで実現されます。

switch v := i.(type) {
case int:
    // v は int 型
case string:
    // v は string 型
default:
    // v は i の元のインターフェース型
}

型スイッチの各case節では、インターフェース値が特定の型である場合に実行されるコードブロックを定義します。case節内で宣言される変数(上記の例ではv)は、そのcase節のスコープ内では、対応する具体的な型を持つことになります。これにより、型アサーションを繰り返し行うことなく、簡潔に複数の型を処理できます。

技術的詳細

test/typeswitch.goファイルは、Go言語の型スイッチの様々な側面をテストするために設計されています。このテストは、主に以下の3つのスタイルで型スイッチの動作を検証しています。

1. 型ガードスタイル (Type Guard Style)

このスタイルは、switch文の条件式にtrueを使用し、各case節で型アサーションを行う形式です。これは、型スイッチが導入される前のGo言語で、複数の型アサーションをif-else ifの連鎖で行っていたパターンをswitch文で表現したものです。

switch v := f(i); true { // v は interface{} 型
case x := v.(bool):     // x は bool 型
    // ...
case x := v.(int):      // x は int 型
    // ...
// ...
}

この形式では、vswitch文のスコープ全体でinterface{}型として扱われますが、各case節内で宣言される変数xは、そのcase節の型アサーションが成功した場合に、具体的な型を持つことになります。このテストは、型アサーションが正しく機能し、対応する型に値が変換されることを確認します。

2. 型スイッチスタイル (Type Switch Style)

これはGo言語の標準的な型スイッチの構文です。switch文の式部分に.(type)を使用します。

switch x := f(i).(type) { // x は各 case 節で具体的な型を持つ
case bool:
    // x は bool 型
case int:
    // x は int 型
// ...
}

この形式では、switch文の初期化ステートメントで宣言される変数xは、各case節のスコープ内で、そのcase節に対応する具体的な型を持つことになります。これにより、コードがより簡潔になり、型安全性が向上します。このテストは、この主要な型スイッチの構文が、様々な型に対して期待通りに動作することを確認します。

3. キャッチオールスタイル (Catch-all Style) およびその他のテストケース

テストファイルには、上記2つの主要なスタイルに加えて、switch文の一般的な動作や、特定のGoの機能(マップの参照、チャネルの受信)とswitch文の組み合わせを検証するケースも含まれています。

  • タグなしスイッチのテスト: switch trueswitch falseのような、式を持たないswitch文の基本的な動作を検証します。これは、if-else ifの代替としてのswitch文の柔軟性を示しています。
  • 型ガードの誤った型アサーション: switch true { case x := f(Int).(float): ... }のように、意図的に間違った型アサーションを試み、それが正しく失敗することを確認します。
  • マップの参照: switch true { case x := m["7"]: ... }のように、マップの要素アクセスをswitch文のcase節で行う場合の動作をテストします。
  • チャネルの受信: switch true { case x := <-c: ... }のように、チャネルからの値の受信をswitch文のcase節で行う場合の動作をテストします。これは、チャネル操作がswitch文のcaseとして機能することを示しています。

これらのテストケースは、型スイッチだけでなく、Go言語のswitch文全般の堅牢性と、他の言語機能との相互作用を包括的に検証することを目的としています。

コアとなるコードの変更箇所

このコミットで追加された唯一のファイルは test/typeswitch.go です。

--- a/test/typeswitch.go
+++ b/test/typeswitch.go
@@ -0,0 +1,173 @@
+// $G $F.go && $L $F.$A && ./$A.out
+
+// 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
+
+const (
+	Bool = iota;
+	Int;
+	Float;
+	String;
+	Struct;
+	Chan;
+	Array;
+	Map;
+	Func;
+	Last;
+)
+
+type S struct { a int }
+var s S = S{1234}
+
+var c = make(chan int);
+
+var a	= []int{0,1,2,3}
+
+var m = make(map[string]int)
+
+func assert(b bool, s string) {
+	if !b {
+		println(s);
+		sys.Exit(1);
+	}
+}
+
+
+func f(i int) interface{} {
+	switch i {
+	case Bool:
+		return true;
+	case Int:
+		return 7;
+	case Float:
+		return 7.4;
+	case String:
+		return "hello";
+	case Struct:
+		return s;
+	case Chan:
+		return c;
+	case Array:
+		return a;
+	case Map:
+		return m;
+	case Func:
+		return f;
+	}
+	panic("bad type number");
+}
+
+func main() {
+	// type guard style
+	for i := Bool; i < Last; i++ {
+		switch v := f(i); true {
+		case x := v.(bool):
+			assert(x == true && i == Bool, "switch 1 bool");
+		case x := v.(int):
+			assert(x == 7 && i == Int, "switch 1 int");
+		case x := v.(float):
+			assert(x == 7.4 && i == Float, "switch 1 float");
+		case x := v.(string):
+			assert(x == "hello" && i == String, "switch 1 string");
+		case x := v.(S):
+			assert(x.a == 1234 && i == Struct, "switch 1 struct");
+		case x := v.(chan int):
+			assert(x == c && i == Chan, "switch 1 chan");
+		case x := v.([]int):
+			assert(x[3] == 3 && i == Array, "switch 1 array");
+		case x := v.(map[string]int):
+			assert(x == m && i == Map, "switch 1 map");
+		case x := v.(func(i int) interface{}):
+			assert(x == f && i == Func, "switch 1 fun");
+		default:
+			assert(false, "switch 1 unknown");
+		}
+	}
+
+	// type switch style
+	for i := Bool; i < Last; i++ {
+		switch x := f(i).(type) {
+		case bool:
+			assert(x == true, "switch 2 bool");
+		case int:
+			assert(x == 7, "switch 2 int");
+		case float:
+			assert(x == 7.4, "switch 2 float");
+		case string:
+			assert(x == "hello", "switch 2 string");
+		case S:
+			assert(x.a == 1234, "switch 2 struct");
+		case chan int:
+			assert(x == c, "switch 2 chan");
+		case []int:
+			assert(x[3] == 3, "switch 2 array");
+		case map[string]int:
+			assert(x == m, "switch 2 map");
+		case func(i int) interface{}:
+			assert(x == f, "switch 2 fun");
+		default:
+			assert(false, "switch 2 unknown");
+		}
+	}
+
+	// catch-all style in various forms
+	switch {
+	case true:
+		assert(true, "switch 3 bool");
+	default:
+		assert(false, "switch 3 unknown");
+	}
+
+	switch true {
+	case true:
+		assert(true, "switch 3 bool");
+	default:
+		assert(false, "switch 3 unknown");
+	}
+
+	switch false {
+	case false:
+		assert(true, "switch 4 bool");
+	default:
+		assert(false, "switch 4 unknown");
+	}
+
+	switch true {
+	case x := f(Int).(float):
+		assert(false, "switch 5 type guard wrong type");
+	case x := f(Int).(int):
+		assert(x == 7, "switch 5 type guard");
+	default:
+		assert(false, "switch 5 unknown");
+	}
+
+	m["7"] = 7;
+	switch true {
+	case x := m["6"]:
+		assert(false, "switch 6 map reference wrong");
+	case x := m["7"]:
+		assert(x == 7, "switch 6 map reference");
+	default:
+		assert(false, "switch 6 unknown");
+	}
+
+	go func() { <-c; c <- 77; } ();
+	// guarantee the channel is ready
+	c <- 77;
+	for i := 0; i < 5; i++ {
+		sys.Gosched();
+	}
+	dummyc := make(chan int);
+	switch true {
+	case x := <-dummyc:
+		assert(false, "switch 7 chan wrong");
+	case x := <-c:
+		assert(x == 77, "switch 7 chan");
+	default:
+		assert(false, "switch 7 unknown");
+	}
+
+}

コアとなるコードの解説

test/typeswitch.goは、Go言語の型スイッチの動作を検証するための自己完結型のテストプログラムです。

  1. 定数とグローバル変数:

    • constブロックでiotaを使用して、テスト対象の様々な型に対応する整数定数(Bool, Int, Float, String, Struct, Chan, Array, Map, Func, Last)を定義しています。これらは、f関数でどの型の値を返すかを制御するために使用されます。
    • Sというシンプルな構造体と、そのインスタンスs、チャネルc、スライスa、マップmがグローバル変数として宣言されています。これらは、f関数が返す具体的な値として使用されます。
  2. assert関数:

    • テストの基本的なアサーションを行うヘルパー関数です。bfalseの場合、エラーメッセージsを出力し、sys.Exit(1)でプログラムを終了させます。これは、Goのテストフレームワークがまだ成熟していなかった初期のテストコードでよく見られるパターンです。
  3. f関数:

    • f(i int) interface{}というシグネチャを持つ関数です。引数i(上記の定数に対応)に基づいて、異なる型の値をinterface{}型として返します。これにより、型スイッチのテストで様々な型の値を動的に生成できます。
  4. main関数:

    • テストの主要なロジックが含まれています。
    • type guard style:
      • forループでBoolからLastまでの各型を繰り返し処理します。
      • switch v := f(i); trueという形式のswitch文を使用しています。vinterface{}型です。
      • case節では、x := v.(type)という型アサーションを行い、xが期待される型と値を持っているか、そして元のiが対応する定数であるかをassert関数で検証します。
    • type switch style:
      • 同様にforループで各型を処理します。
      • switch x := f(i).(type)というGoの標準的な型スイッチの構文を使用しています。この場合、xは各case節のスコープ内で具体的な型を持ちます。
      • case節で、xの値が期待通りであるかをassert関数で検証します。
    • catch-all style in various forms:
      • タグなしswitch文(switch {}switch true {}switch false {})の基本的な動作をテストします。
      • 意図的に間違った型アサーションを行うケース(f(Int).(float))をテストし、それがdefaultにフォールバックするか、あるいはコンパイルエラーにならないことを確認します(この場合は実行時にassert(false, ...)が呼ばれることでテスト失敗となる)。
      • マップの要素アクセス(m["7"])やチャネルからの受信(<-c)がswitch文のcase節でどのように機能するかをテストします。特にチャネルのテストでは、ゴルーチンとsys.Gosched()(スケジューラに制御を戻す)を使って、チャネルの準備が整うのを待つような、当時のGoの並行処理テストの典型的なパターンが見られます。

このテストファイルは、Go言語の型スイッチが、様々なデータ型、インターフェース、そして他の言語機能との組み合わせにおいて、期待通りに動作することを網羅的に検証しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメントおよび仕様書
  • Go言語のGitHubリポジトリのコミット履歴
  • Go言語の型スイッチに関する一般的な解説記事 (Web検索)
    • 例: "Go type switch", "Go interface empty interface" などのキーワードで検索し、Go言語の初期の設計思想や型システムの解説を参考にしました。
    • 具体的なURLは、一般的なGo言語の概念に関するものであり、特定の記事を直接参照したわけではありません。