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

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

このコミットは、Go言語の標準ライブラリであるfmtパッケージ内のprint.goファイルに対するバグ修正と機能追加を扱っています。具体的には、bool型の値の出力が適切に処理されていなかった問題と、float64型の値の取得における型アサーションの誤りを修正しています。

コミット

commit 59f029cbf241b5f29e183320c417ce5059464ccd
Author: Rob Pike <r@golang.org>
Date:   Sat Nov 1 16:37:53 2008 -0700

    a couple of bugs in print.
    1) bool wasn't handled (added '%t' for 'truth').
    2) float64 had a typo.
    
    TBR=rsc
    DELTA=11  (10 added, 0 deleted, 1 changed)
    OCL=18314
    CL=18318

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

https://github.com/golang/go/commit/59f029cbf241b5f29e183320c417ce5059464ccd

元コミット内容

a couple of bugs in print.
1) bool wasn't handled (added '%t' for 'truth').
2) float64 had a typo.

TBR=rsc
DELTA=11  (10 added, 0 deleted, 1 changed)
OCL=18314
CL=18318

変更の背景

このコミットは、Go言語がまだ開発の初期段階にあった2008年に行われたものです。Go言語のfmtパッケージは、C言語のprintfに似た書式指定文字列を用いたフォーマット済みI/Oを提供することを目的としています。しかし、初期の実装では、すべてのデータ型が適切に処理されているわけではありませんでした。

このコミットの主な背景は以下の2点です。

  1. bool型の出力の欠如: 当時のfmtパッケージのprint機能は、bool型の値を適切に文字列として表現するメカニズムが不足していました。これにより、ブール値を直接出力しようとすると、予期せぬ結果になったり、エラーが発生したりする可能性がありました。この問題を解決するため、ブール値専用のフォーマット指定子%tが導入されました。
  2. float64型の処理におけるバグ: 浮動小数点数、特にfloat64型の値を内部的に処理する際に、誤った型アサーション(reflect.Float32Valueと誤って扱っていた)が存在していました。これは、float64の正確な値が取得されず、結果として誤った出力につながる可能性のある潜在的なバグでした。

これらの問題は、fmtパッケージの正確性と完全性を確保するために修正される必要がありました。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語の基本的な概念と、当時のreflectパッケージの動作に関する知識が必要です。

1. Go言語のfmtパッケージ

fmtパッケージは、Go言語におけるフォーマット済みI/O(入出力)を扱うための標準パッケージです。C言語のprintfscanfに似た関数を提供し、様々なデータ型を整形して文字列として出力したり、文字列からデータを読み取ったりする機能を持っています。

  • Printf系の関数: 書式指定文字列(例: %d%s%fなど)を使用して、引数の値を指定された形式で文字列に変換し、出力します。
  • Print系の関数: 引数をデフォルトの形式で出力します。
  • Println系の関数: 引数をデフォルトの形式で出力し、最後に改行を追加します。

2. Go言語のreflectパッケージ

reflectパッケージは、Goプログラムが実行時に自身の構造(型情報)を検査し、操作するための機能を提供します。これにより、ジェネリックなコードを書いたり、異なる型の値を動的に扱ったりすることが可能になります。

  • reflect.Value: 実行時のGoの値を表す型です。この型を通じて、値の取得、設定、メソッドの呼び出しなどが行えます。
  • reflect.Kind: 型の基本的なカテゴリ(例: IntStringStructBoolFloat64など)を表す列挙型です。reflect.ValueからKind()メソッドを使って取得できます。
  • 型アサーション: インターフェース型に格納されている具体的な値の型を、実行時にチェックし、その具体的な型として扱うための構文です。例えば、v.(reflect.Float32Value)は、vreflect.Float32Value型であることをアサートしています。

3. printfスタイルのフォーマット指定子

printf系の関数で使用される特殊な文字の組み合わせで、引数の値をどのように整形して出力するかを指示します。

  • %d: 整数を10進数で出力。
  • %s: 文字列を出力。
  • %f: 浮動小数点数を標準形式で出力。
  • %t: (このコミットで追加された)ブール値をtrueまたはfalseとして出力。

技術的詳細

このコミットは、src/lib/fmt/print.goファイル内の2つの主要な領域に焦点を当てた修正と機能追加を行っています。

1. getFloat関数におけるfloat64の型アサーションの修正

getFloat関数は、reflect.Valueから浮動小数点数(float32またはfloat64)の値を取得することを目的としています。元のコードでは、reflect.Float64Kindの場合に誤ってv.(reflect.Float32Value).Get()を使用していました。これは、float64の値をfloat32として扱おうとするものであり、データの精度が失われたり、不正な値が返されたりする可能性のあるバグでした。

このコミットでは、この行をreturn float64(v.(reflect.Float64Value).Get()), true;に修正し、float64型の値はreflect.Float64Valueとして正しくアサートされるようにしました。これにより、float64の値が正確に取得され、fmtパッケージの浮動小数点数出力の信頼性が向上しました。

2. doprintf関数における%tフォーマット指定子の追加とbool型の処理

doprintf関数は、書式指定文字列に基づいて値をフォーマットする主要なロジックを含んでいます。このコミット以前は、bool型を直接フォーマットするための特定の指定子が存在しませんでした。

  • %t指定子の導入: 新たにcase 't':が追加され、フォーマット指定子%tbool型のために予約されました。
  • reflect.BoolValueの処理: field.(reflect.BoolValue).Get()を使用してbool型の実際の値を取得し、その値がtrueであれば文字列"true"を、falseであれば文字列"false"を生成するようにロジックが追加されました。これにより、fmt.Printf("%t", true)のような呼び出しが期待通りに動作するようになりました。

3. doprint関数におけるreflect.BoolKindの追加

doprint関数は、PrintlnPrintのような、書式指定文字列なしで値をデフォルト形式で出力する関数によって内部的に使用されます。この関数は、値のKind()に基づいて異なる型の処理を分岐します。

  • reflect.BoolKindの追加: switch field.Kind()の中にcase reflect.BoolKind:が追加されました。これにより、bool型の値が渡された際に、p.fmt.boolean(field.(reflect.BoolValue).Get()).str()という新しいロジックが実行されるようになりました。このロジックは、bool値を"true"または"false"の文字列に変換する役割を担っています。

これらの変更により、Go言語のfmtパッケージは、bool型の値をより堅牢かつ正確に処理できるようになり、開発者がブール値を期待通りに整形して出力できるようになりました。

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

--- a/src/lib/fmt/print.go
+++ b/src/lib/fmt/print.go
@@ -197,7 +197,7 @@ func getFloat(v reflect.Value) (val float64, ok bool) {
 	case reflect.Float32Kind:
 		return float64(v.(reflect.Float32Value).Get()), true;
 	case reflect.Float64Kind:
-		return float64(v.(reflect.Float32Value).Get()), true;
+		return float64(v.(reflect.Float64Value).Get()), true;
 	case reflect.Float80Kind:
 		break;	// TODO: what to do here?
 	}
@@ -273,6 +273,14 @@ func (p *P) doprintf(format string, v reflect.StructValue) {
 		fieldnum++;
 		s := "";
 		switch c {
+			// bool
+			case 't':
+				if field.(reflect.BoolValue).Get() {
+					s = "true";
+				} else {
+					s = "false";
+				}
+
 			// int
 			case 'b':
 				if v, signed, ok := getInt(field); ok {
@@ -369,6 +377,8 @@ func (p *P) doprint(v reflect.StructValue, is_println bool) {
 			p.add(' ')
 		}
 		switch field.Kind() {
+		case reflect.BoolKind:
+			s = p.fmt.boolean(field.(reflect.BoolValue).Get()).str();
 		case reflect.IntKind, reflect.Int8Kind, reflect.Int16Kind, reflect.Int32Kind, reflect.Int64Kind:
 			v, signed, ok := getInt(field);
 			s = p.fmt.d64(v).str();

コアとなるコードの解説

1. src/lib/fmt/print.gogetFloat 関数

@@ -197,7 +197,7 @@ func getFloat(v reflect.Value) (val float64, ok bool) {
 	case reflect.Float32Kind:
 		return float64(v.(reflect.Float32Value).Get()), true;
 	case reflect.Float64Kind:
-		return float64(v.(reflect.Float32Value).Get()), true;
+		return float64(v.(reflect.Float64Value).Get()), true;
 	case reflect.Float80Kind:
 		break;	// TODO: what to do here?
 	}
  • 変更前: case reflect.Float64Kind: の行で、float64型の値を処理する際に、誤ってv.(reflect.Float32Value).Get()と記述されていました。これは、reflect.Valuefloat64型であるにもかかわらず、float32Valueとしてアサートしようとしていたため、型アサーションの失敗や、値の不正な変換を引き起こす可能性がありました。
  • 変更後: return float64(v.(reflect.Float64Value).Get()), true; に修正されました。これにより、float64型の値はreflect.Float64Valueとして正しくアサートされ、その値が正確に取得されるようになりました。これは、fmtパッケージが浮動小数点数を正しく扱うための重要なバグ修正です。

2. src/lib/fmt/print.godoprintf 関数

@@ -273,6 +273,14 @@ func (p *P) doprintf(format string, v reflect.StructValue) {
 		fieldnum++;
 		s := "";
 		switch c {
+			// bool
+			case 't':
+				if field.(reflect.BoolValue).Get() {
+					s = "true";
+				} else {
+					s = "false";
+				}
+
 			// int
 			case 'b':
 				if v, signed, ok := getInt(field); ok {
  • 追加箇所: switch c(フォーマット指定子を処理する部分)の中に、新たにcase 't':が追加されました。
  • 機能: このブロックは、フォーマット指定子%tが検出された場合に実行されます。field.(reflect.BoolValue).Get()を使って、現在のフィールドがreflect.BoolValueであることをアサートし、そのブール値を取得します。取得した値がtrueであれば文字列"true"を、falseであれば文字列"false"s変数に代入します。これにより、fmt.Printf("%t", someBoolVar)のようにブール値を整形して出力できるようになりました。

3. src/lib/fmt/print.godoprint 関数

@@ -369,6 +377,8 @@ func (p *P) doprint(v reflect.StructValue, is_println bool) {
 			p.add(' ')
 		}
 		switch field.Kind() {
+		case reflect.BoolKind:
+			s = p.fmt.boolean(field.(reflect.BoolValue).Get()).str();
 		case reflect.IntKind, reflect.Int8Kind, reflect.Int16Kind, reflect.Int32Kind, reflect.Int64Kind:
 			v, signed, ok := getInt(field);
 			s = p.fmt.d64(v).str();
  • 追加箇所: switch field.Kind()(値のKindに基づいて処理を分岐する部分)の中に、新たにcase reflect.BoolKind:が追加されました。
  • 機能: このブロックは、fieldKindreflect.BoolKind(つまり、値がブール型)である場合に実行されます。p.fmt.boolean(field.(reflect.BoolValue).Get()).str()という呼び出しは、reflect.BoolValueからブール値を取得し、それをfmtパッケージ内部のヘルパー関数booleanに渡して文字列("true"または"false")に変換し、その結果をs変数に代入します。これにより、fmt.Print(someBoolVar)fmt.Println(someBoolVar)のように、書式指定なしでブール値を出力する際に、適切に文字列化されるようになりました。

これらの変更は、Go言語のfmtパッケージが、ブール型とfloat64型をより正確かつ柔軟に扱えるようにするための基盤を築きました。

関連リンク

参考にした情報源リンク