[インデックス 1419] ファイルの概要
このコミットは、Go言語の標準ライブラリの一部であるsrc/lib/utf8_test.go
ファイルに対する変更です。具体的には、UTF-8エンコーディングのテストに関連するヘルパー関数Bytes
内のバイトスライス([]byte
)の初期化方法を修正しています。
コミット
commit 344b16512cd8e6883302617f4727381587d87305
Author: Rob Pike <r@golang.org>
Date: Tue Jan 6 15:30:07 2009 -0800
update utf8_test.go
R=rsc
OCL=22170
CL=22170
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/344b16512cd8e6883302617f4727381587d87305
元コミット内容
update utf8_test.go
R=rsc
OCL=22170
CL=22170
変更の背景
このコミットは、Go言語の非常に初期の段階(2009年1月)に行われたものです。当時のGo言語はまだ開発途上にあり、言語仕様や標準ライブラリのAPIが頻繁に変更されていました。この変更は、バイトスライスの正しい初期化方法に関する修正であり、new
とmake
というGo言語の2つの異なるメモリ割り当て関数のセマンティクスが、開発初期段階でまだ完全に確立されていなかった、あるいは誤用されていた可能性を示唆しています。
utf8_test.go
は、Go言語がUTF-8エンコーディングを正しく処理できることを保証するためのテストファイルです。このテストファイル内のヘルパー関数が正しく動作しない場合、UTF-8関連の機能のテストが正確に行われず、潜在的なバグを見逃す可能性があります。したがって、この修正はテストインフラの信頼性を向上させるための重要な変更でした。
前提知識の解説
Go言語におけるnew
とmake
Go言語には、メモリを割り当てるための組み込み関数としてnew
とmake
の2つがあります。これらは似ていますが、異なる目的で使用されます。
-
new(T)
:- 型
T
のゼロ値のためのメモリを割り当て、そのアドレス(*T
)を返します。 new
は、ポインタを返します。- 任意の型に対して使用できます。
- 割り当てられたメモリは、その型のゼロ値で初期化されます(例: 整数は0、文字列は空文字列、スライスは
nil
)。 - 例:
p := new(int)
は*p
が0
であるint
型のポインタを返します。
- 型
-
make(T, args)
:- スライス(
[]T
)、マップ(map[K]V
)、チャネル(chan T
)の3つの組み込み参照型のみに使用できます。 - これらの型を初期化し、使用可能な状態にします。
make
はポインタではなく、初期化された(ゼロ値ではない)型T
の値を返します。 - スライスの場合、
make([]T, length, capacity)
のように、長さ(length
)とオプションで容量(capacity
)を指定して、基になる配列を割り当て、スライスヘッダを初期化します。 - 例:
s := make([]int, 10)
は、長さ10のint
スライスを作成し、その要素をゼロ値で初期化します。
- スライス(
スライス([]byte
)の内部構造
Go言語のスライスは、基になる配列への参照、長さ、容量の3つの要素から構成される構造体です。
- ポインタ: スライスが参照する基になる配列の先頭要素へのポインタ。
- 長さ(Length): スライスに含まれる要素の数。
- 容量(Capacity): スライスの基になる配列のサイズ。スライスを拡張できる最大値。
new([]byte, len(s)+1)
と書いた場合、これは[]byte
型のゼロ値(つまり、ポインタがnil
、長さが0、容量が0のスライスヘッダ)へのポインタを割り当てるだけで、実際にlen(s)+1
バイトのメモリを持つ基になる配列は割り当てられません。そのため、このスライスに対して要素を書き込もうとすると、ランタイムエラー(パニック)が発生するか、未定義の動作を引き起こす可能性があります。
一方、make([]byte, len(s)+1)
と書いた場合、これはlen(s)+1
バイトの基になる配列を割り当て、その配列を指すようにスライスヘッダを適切に初期化します。これにより、スライスは指定された長さのバイトを保持する準備が整います。
syscall.StringToBytes
(当時のGo言語における内部関数)
このコミットが行われた2009年当時、syscall.StringToBytes
という関数がGo言語の内部で使用されていました。この関数は、Goの文字列(不変のバイト列)を、変更可能なバイトスライスに効率的にコピーするために設計されていたと考えられます。現代のGo言語では、文字列をバイトスライスに変換するには、単に[]byte(s)
のように型変換を行うのが一般的です。syscall
パッケージは、OSのシステムコールにアクセスするためのものであり、このような文字列変換関数がsyscall
パッケージ内に存在していたのは、当時のGo言語の設計がまだ流動的であったこと、あるいは特定のプラットフォーム固有の最適化を意図していた可能性を示唆しています。
技術的詳細
このコミットの核心は、Go言語におけるスライスの正しい初期化とメモリ割り当てに関する理解の深化にあります。
元のコード:
b := new([]byte, len(s)+1);
この行は、[]byte
型のゼロ値(nil
スライス)へのポインタをb
に割り当てます。しかし、syscall.StringToBytes
関数が期待するのは、実際にメモリが割り当てられ、書き込み可能な状態になっているバイトスライスです。new
関数はスライスヘッダのためのメモリを割り当てるだけで、そのスライスが参照する基になるバイト配列は割り当てません。結果として、syscall.StringToBytes
がこのb
に対してバイトを書き込もうとすると、基になる配列が存在しないため、パニックや不正なメモリアクセスが発生する可能性がありました。
修正後のコード:
b := make([]byte, len(s)+1);
この行は、len(s)+1
の長さと容量を持つ新しいバイトスライスをb
に割り当てます。make
関数は、スライスヘッダだけでなく、そのスライスが参照する基になるバイト配列も実際に割り当て、ゼロ値で初期化します。これにより、b
はlen(s)+1
バイトのデータを保持できる状態になり、syscall.StringToBytes
関数が安全に文字列のバイトをコピーできるようになります。
この変更は、Go言語の初期開発段階における、new
とmake
のセマンティクスに関する理解の成熟を示すものです。特に、スライス、マップ、チャネルといった参照型を正しく初期化するためにはmake
を使用する必要があるという、Go言語の基本的な設計原則が明確になったことを意味します。
コアとなるコードの変更箇所
--- a/src/lib/utf8_test.go
+++ b/src/lib/utf8_test.go
@@ -45,7 +45,7 @@ var utf8map = []Utf8Map {
}
func Bytes(s string) []byte {
- b := new([]byte, len(s)+1);
+ b := make([]byte, len(s)+1);
if !syscall.StringToBytes(b, s) {
panic("StringToBytes failed");
}
コアとなるコードの解説
変更はsrc/lib/utf8_test.go
ファイル内のBytes
関数にあります。
Bytes
関数は、入力として文字列s
を受け取り、その文字列のバイト表現を含むバイトスライスを返すことを目的としたヘルパー関数です。
-
変更前:
b := new([]byte, len(s)+1);
- この行は、
[]byte
型のゼロ値(nil
スライス)へのポインタをb
に割り当てようとしていました。しかし、new
はスライス自体を初期化して基になる配列を割り当てるわけではありません。そのため、b
は有効なバイトスライスとして機能せず、次のsyscall.StringToBytes
呼び出しで問題を引き起こす可能性がありました。
- この行は、
-
変更後:
b := make([]byte, len(s)+1);
- この行は、
len(s)+1
の長さと容量を持つ新しいバイトスライスをb
に割り当てます。make
関数は、スライスヘッダを適切に初期化し、指定された長さの基になるバイト配列を割り当てます。これにより、b
は文字列のバイトを格納するのに十分なメモリを持つ、完全に機能するバイトスライスになります。
- この行は、
-
if !syscall.StringToBytes(b, s)
:- この行は、文字列
s
のバイトを、新しく作成されたバイトスライスb
にコピーしようとします。syscall.StringToBytes
が成功しなかった場合(例えば、b
が有効なスライスでなかった場合)、パニックを発生させます。この修正により、b
が常に有効なスライスとしてsyscall.StringToBytes
に渡されることが保証されます。
- この行は、文字列
この修正は、Go言語の初期段階における、スライスとメモリ割り当てのセマンティクスに関する重要な学習と改善を反映しています。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語の
new
とmake
に関する公式ブログ記事(より現代的な視点から):https://go.dev/blog/go-slices-usage-and-internals (このコミットより後の記事ですが、概念を理解するのに役立ちます)
参考にした情報源リンク
- Go言語のソースコードリポジトリ: https://github.com/golang/go
- Go言語の
new
とmake
に関する一般的な解説記事(多数存在するため、特定のURLは挙げませんが、Go言語の基本的な学習リソースを参照しました) - Go言語の初期のコミット履歴(GitHub上で直接参照)