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

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

このコミットは、Go言語の標準ライブラリである io パッケージに StringBytes というヘルパールーチンを追加するものです。この関数は、文字列をバイトスライスに変換する共通の機能を提供し、特に syscall.StringToBytes が不必要なヌル終端文字を追加してしまう問題を回避することを目的としています。これにより、文字列のマーシャリング(データ構造をメモリやファイル、ネットワーク経由で転送可能な形式に変換すること)が容易になります。

コミット

commit a238087aa22f163fa0bbf6890a451b3919925930
Author: Rob Pike <r@golang.org>
Date:   Wed Dec 10 15:46:45 2008 -0800

    StringBytes help routine, common functionality put into package io for sharing.
    
    R=rsc
    DELTA=10  (10 added, 0 deleted, 0 changed)
    OCL=20928
    CL=20931

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

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

元コミット内容

diff --git a/src/lib/io/io.go b/src/lib/io/io.go
index 9ae9264416..26c2aaab76 100644
--- a/src/lib/io/io.go
+++ b/src/lib/io/io.go
@@ -144,3 +144,13 @@ export func Copy(src Read, dst Write) (written int64, err *os.Error) {
 	return written, err
 }
 
+// Convert a string to an array of bytes for easy marshaling.
+// Could fill with syscall.StringToBytes but it adds an unnecessary \000
+// so the length would be wrong.
+export func StringBytes(s string) *[]byte {
+	b := new([]byte, len(s));
+	for i := 0; i < len(s); i++ {
+		b[i] = s[i];
+	}
+	return b;
+}

変更の背景

このコミットが行われた2008年12月は、Go言語がまだ一般公開される前の初期開発段階にありました。当時のGo言語では、文字列とバイトスライスの間の変換に関して、いくつかの課題がありました。特に、syscall パッケージが提供する StringToBytes のような低レベルな関数は、C言語との相互運用性を考慮して設計されており、文字列の終端にヌル文字 (\000) を自動的に追加する挙動がありました。

しかし、Go言語の文字列はバイトのシーケンスであり、ヌル終端文字を必要としません。また、Goのバイトスライス([]byte)も、その長さ情報が別途管理されるため、ヌル終端文字は不要であり、むしろ余分なバイトとして扱われ、期待される長さと異なる結果を招く可能性がありました。

このような背景から、Go言語の内部や他のパッケージで文字列をバイトスライスに変換する際に、ヌル終端文字の問題を回避し、かつ効率的で正確な方法が必要とされていました。io パッケージは、入出力操作のための基本的なインターフェースとヘルパー関数を提供する場所であり、文字列からバイトへの変換は多くの入出力操作で共通して必要となるため、このパッケージに StringBytes 関数を配置することが適切と判断されました。これにより、共通のユーティリティとして他のコンポーネントから利用できるようになり、コードの重複を避け、一貫性を保つことが可能になりました。

前提知識の解説

このコミットを理解するためには、以下のGo言語の初期の概念と一般的なプログラミングの知識が必要です。

  1. Go言語の初期の構文 (export func, new([]byte, len(s))):

    • export func: 現在のGo言語では、関数名や変数名が大文字で始まることでエクスポート(外部からアクセス可能)されますが、Go言語の非常に初期の段階では、export キーワードが明示的に使用されていました。これは、言語設計の進化の過程で削除された構文です。
    • new([]byte, len(s)): 現在のGo言語では、スライスを初期化する際には make 関数を使用するのが一般的です(例: make([]byte, len(s)))。しかし、初期のGo言語では new 関数を使ってスライスを指すポインタを生成し、そのポインタが指すスライスを初期化するような構文が使われていた時期がありました。このコミットのコードは、その過渡期の構文を示しています。new は型Tのゼロ値へのポインタを返しますが、ここではスライス型 []byte のゼロ値(nilスライス)へのポインタを生成し、その後に len(s) の長さで初期化しているように見えます。ただし、この記述は現在のGoの new の挙動とは異なるため、当時のGoのコンパイラが特別にスライスの初期化として解釈していた可能性があります。より正確には、new はメモリを割り当ててゼロ値を返すため、new([]byte, len(s))len(s) の容量を持つバイトスライスを指すポインタを生成し、そのスライスがゼロ値(バイトの場合は0)で埋められることを意味します。
  2. Go言語における文字列とバイトスライス:

    • 文字列 (string): Go言語の文字列は、不変のバイトのシーケンスです。UTF-8エンコードされたテキストを扱うことが一般的ですが、任意のバイトシーケンスを保持できます。文字列は内部的にポインタと長さで表現され、ヌル終端文字は持ちません。
    • バイトスライス ([]byte): バイトスライスは、可変長のバイトのシーケンスです。これは、Go言語でバイナリデータや、変更可能なテキストデータを扱うためによく使用されます。スライスは、基盤となる配列へのポインタ、長さ、容量の3つの要素で構成されます。
  3. ヌル終端文字 (\000) の問題:

    • C言語のような多くのプログラミング言語では、文字列は文字の配列として表現され、その終端にはヌル文字 (\000 または \0) が置かれることで文字列の終わりを示します。これを「ヌル終端文字列」と呼びます。
    • Go言語の文字列やバイトスライスは、長さ情報を持つため、ヌル終端文字を必要としません。もし、ヌル終端文字が意図せず追加されると、そのバイトスライスの長さが期待よりも長くなり、データの処理に誤りが生じる可能性があります。例えば、ネットワークプロトコルで正確なバイト数を送受信する必要がある場合、余分なヌル文字はプロトコル違反やデータ破損を引き起こす可能性があります。
  4. io パッケージの役割:

    • Go言語の io パッケージは、入出力プリミティブ(Reader, Writer インターフェースなど)と、それらを操作するためのヘルパー関数を提供します。ファイル、ネットワーク接続、メモリバッファなど、様々なソースからのデータの読み書きを抽象化するための基盤となります。文字列からバイトへの変換は、これらの入出力操作において頻繁に必要とされるため、io パッケージにこのようなユーティリティ関数が追加されるのは自然なことです。

技術的詳細

このコミットで追加された StringBytes 関数は、Go言語の文字列をバイトスライスに変換するためのシンプルなループベースのアプローチを採用しています。

関数のシグネチャは export func StringBytes(s string) *[]byte です。

  • s string: 入力として文字列 s を受け取ります。
  • *[]byte: バイトスライスへのポインタを返します。初期のGoでは、スライス自体ではなく、スライスへのポインタを返すことが一般的でした。

関数の実装は以下の通りです。

  1. b := new([]byte, len(s));

    • len(s) は入力文字列 s のバイト長(UTF-8エンコードされた文字数ではなく、バイト数)を返します。
    • new([]byte, len(s)) は、len(s) の長さを持つ新しいバイトスライスをメモリ上に割り当て、そのスライスへのポインタを b に代入します。この時点では、スライスの各バイトはゼロ値(0)で初期化されています。
  2. for i := 0; i < len(s); i++ { b[i] = s[i]; }

    • この for ループは、文字列 s の各バイトを順番に新しいバイトスライス b にコピーします。
    • Go言語の文字列はバイトのシーケンスとしてインデックスアクセスが可能です。s[i] は文字列 si 番目のバイト(byte 型)を返します。
    • b[i] = s[i] は、文字列 si 番目のバイトを、新しく作成したバイトスライス bi 番目の位置にコピーします。

この手動でのバイトコピーは、syscall.StringToBytes が引き起こすヌル終端文字の問題を回避するための直接的な解決策です。syscall.StringToBytes は、C言語の文字列(ヌル終端)を扱うためのものであり、Goの内部的な文字列表現とは異なるセマンティクスを持っていました。そのため、Goの文字列をそのままバイトスライスとして扱いたい場合には、この手動コピーが最も安全で正確な方法でした。

このアプローチの利点は、変換されるバイトスライスの長さが正確に元の文字列の長さと一致すること、そして余分なバイト(ヌル終端文字など)が含まれないことです。これは、特にバイナリプロトコルや、正確なデータ長が要求される場面で非常に重要です。

現在のGo言語では、文字列をバイトスライスに変換する最も一般的な方法は []byte(s) のように型変換を使用することです。これはコンパイラによって最適化され、多くの場合、メモリの再割り当てなしに効率的に変換が行われます。このコミットの StringBytes 関数は、Go言語がまだ初期段階であり、このような最適化された型変換が利用できなかった、あるいはその挙動がまだ確立されていなかった時代の名残と言えます。

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

src/lib/io/io.go ファイルに以下のコードが追加されました。

--- a/src/lib/io/io.go
+++ b/src/lib/io/io.go
@@ -144,3 +144,13 @@ export func Copy(src Read, dst Write) (written int64, err *os.Error) {
 	return written, err
 }
 
+// Convert a string to an array of bytes for easy marshaling.
+// Could fill with syscall.StringToBytes but it adds an unnecessary \000
+// so the length would be wrong.
+export func StringBytes(s string) *[]byte {
+	b := new([]byte, len(s));
+	for i := 0; i < len(s); i++ {
+		b[i] = s[i];
+	}
+	return b;
+}

コアとなるコードの解説

追加された StringBytes 関数は、文字列 s をバイトスライスに変換します。

// Convert a string to an array of bytes for easy marshaling.
// Could fill with syscall.StringToBytes but it adds an unnecessary \000
// so the length would be wrong.
export func StringBytes(s string) *[]byte {
	// 入力文字列 s と同じ長さの新しいバイトスライスを割り当て、そのポインタを b に格納します。
	// new([]byte, len(s)) は、len(s) の長さを持つバイトスライスをゼロ値で初期化し、
	// そのスライスへのポインタを返します。
	b := new([]byte, len(s));
	// ループを使って、文字列 s の各バイトを新しいバイトスライス b にコピーします。
	// Goの文字列はバイトのシーケンスとして扱えるため、s[i] で i 番目のバイトにアクセスできます。
	for i := 0; i < len(s); i++ {
		b[i] = s[i];
	}
	// 完成したバイトスライスへのポインタを返します。
	return b;
}

この関数は、文字列の各文字(バイト)を新しいバイトスライスに手動でコピーすることで、syscall.StringToBytes が引き起こすヌル終端文字の問題を回避しています。これにより、文字列の正確なバイト表現が得られ、特にバイナリデータのマーシャリングにおいて、予期せぬ長さの不一致を防ぐことができます。

関連リンク

参考にした情報源リンク

  • Go言語の初期の構文に関する情報 (例: export キーワード):
    • Go言語のリリースノートや古いドキュメント、またはGo言語の歴史に関するブログ記事や論文。
    • Go言語のソースコードリポジトリのコミット履歴 (特に初期のコミット)。
  • Go言語における文字列とバイトスライスの変換に関する議論:
    • Go言語のメーリングリストやフォーラムのアーカイブ。
    • Go言語の設計に関する論文やブログ記事。
  • C言語のヌル終端文字列に関する一般的な情報。
  • syscall パッケージの歴史と目的に関する情報。

(注: 2008年当時のGo言語の具体的なドキュメントや議論の直接的なリンクを見つけるのは困難な場合があります。上記の「参考にした情報源リンク」は、一般的な情報源のカテゴリを示しています。)