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

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

このコミットは、Go言語の標準ライブラリであるbufioパッケージとioパッケージにおいて、Unicode文字(コードポイント)を扱う際の型定義をintから組み込み型runeへ変更するものです。具体的には、ReadRuneおよびWriteRune関数のシグネチャが更新され、よりGo言語のイディオムに沿った記述になっています。

コミット

commit 9f6d036f333effed712e54c0e087b4be616d2dab
Author: Russ Cox <rsc@golang.org>
Date:   Tue Oct 25 22:23:34 2011 -0700

    bufio, io: use rune
    
    ReadRune, WriteRune change signature.
    
    R=golang-dev, gri, r
    CC=golang-dev
    https://golang.org/cl/5314043

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

https://github.com/golang/go/commit/9f6d036f333effed712e54c0e087b4be616d2dab

元コミット内容

bufioおよびioパッケージにおいて、rune型を使用するように変更。 ReadRuneおよびWriteRune関数のシグネチャが変更されました。

変更の背景

Go言語では、文字列はUTF-8でエンコードされたバイトのシーケンスとして扱われます。しかし、Unicodeのコードポイント(文字)を直接扱う必要がある場合、Goにはruneという組み込み型が用意されています。runeは実際にはint32のエイリアスであり、単一のUnicodeコードポイントを表します。

このコミット以前は、bufio.Reader.ReadRune()bufio.Writer.WriteRune()、そしてio.RuneReaderインターフェースのReadRune()メソッドの戻り値や引数で、Unicodeコードポイントをint型として扱っていました。これは機能的には問題ありませんでしたが、Go言語の設計思想やイディオムからすると、Unicodeコードポイントを明示的に示すrune型を使用する方が、コードの意図が明確になり、可読性や保守性が向上します。

この変更は、Go言語がUnicodeとUTF-8を第一級市民として扱うという設計哲学をさらに強化し、APIの一貫性を高めるためのものです。これにより、開発者はUnicode文字を扱う際に、より直感的で型安全なコードを書くことができるようになります。

前提知識の解説

Go言語におけるrune

Go言語において、string型は不変のバイトスライスであり、通常はUTF-8でエンコードされたテキストを表します。しかし、stringを直接イテレートすると、バイト単位で処理されるため、マルチバイト文字(UTF-8で2バイト以上を占める文字)を正しく扱うことができません。

ここで登場するのがrune型です。runeはGoの組み込み型で、単一のUnicodeコードポイントを表すために使用されます。その実体はint32のエイリアスであり、Unicodeの各文字に割り当てられた数値(コードポイント)を格納します。

例:

  • A (U+0041) は rune(65)
  • (U+3042) は rune(12354)

for rangeループで文字列をイテレートすると、各要素はrune型として取得され、UTF-8のデコードが自動的に行われます。

s := "Hello, 世界"
for i, r := range s {
    fmt.Printf("Index: %d, Rune: %c (U+%04X)\n", i, r, r)
}

出力:

Index: 0, Rune: H (U+0048)
Index: 1, Rune: e (U+0065)
Index: 2, Rune: l (U+006C)
Index: 3, Rune: l (U+006C)
Index: 4, Rune: o (U+006F)
Index: 5, Rune: , (U+002C)
Index: 6, Rune:   (U+0020)
Index: 7, Rune: 世 (U+4E16)
Index: 10, Rune: 界 (U+754C)

(インデックスが飛んでいるのは、マルチバイト文字が複数のバイトを占めるためです。)

UTF-8エンコーディング

UTF-8は、Unicode文字を可変長のバイトシーケンスで表現するエンコーディング方式です。ASCII文字は1バイトで表現され、それ以外の文字は2バイトから4バイトで表現されます。この可変長エンコーディングにより、ASCIIとの互換性を保ちつつ、世界中のあらゆる文字を効率的に表現できます。

Go言語の標準ライブラリには、unicode/utf8パッケージがあり、UTF-8エンコーディングとデコーディングのためのユーティリティ関数が提供されています。例えば、utf8.DecodeRuneはバイトスライスからruneとバイトサイズを抽出し、utf8.EncodeRuneruneをバイトスライスにエンコードします。

bufioパッケージ

bufioパッケージは、I/O操作をバッファリングすることで効率化するための機能を提供します。これにより、ディスクやネットワークへのアクセス回数を減らし、パフォーマンスを向上させることができます。

  • bufio.Reader: バッファリングされた読み込みを提供します。ReadByteReadRuneReadLineなどのメソッドがあります。
  • bufio.Writer: バッファリングされた書き込みを提供します。WriteByteWriteRuneWriteStringなどのメソッドがあります。

ioパッケージ

ioパッケージは、I/Oプリミティブの基本的なインターフェースを提供します。ReaderWriterCloserなどのインターフェースが定義されており、これらを実装することで、様々なI/Oソース(ファイル、ネットワーク接続、メモリなど)に対して統一的な方法で操作を行うことができます。

このコミットで関連するのは、io.RuneReaderインターフェースです。これはReadRune()メソッドを持つインターフェースで、Unicodeコードポイントを読み込む機能を提供します。

技術的詳細

このコミットの主要な変更点は、bufioおよびioパッケージ内のReadRuneおよびWriteRune関連の関数シグネチャと変数宣言において、int型を使用していた箇所を明示的にrune型に変更したことです。

具体的には、以下の変更が行われました。

  1. bufio.Reader.ReadRune()のシグネチャ変更: 変更前: func (b *Reader) ReadRune() (rune int, size int, err os.Error) 変更後: func (b *Reader) ReadRune() (r rune, size int, err os.Error) 戻り値の最初の要素の名前がruneからrに変更され、型がintからruneに明示されました。

  2. bufio.Writer.WriteRune()のシグネチャ変更: 変更前: func (b *Writer) WriteRune(rune int) (size int, err os.Error) 変更後: func (b *Writer) WriteRune(r rune) (size int, err os.Error) 引数の名前がruneからrに変更され、型がintからruneに明示されました。

  3. io.RuneReaderインターフェースのシグネチャ変更: 変更前: type RuneReader interface { ReadRune() (rune int, size int, err os.Error) } 変更後: type RuneReader interface { ReadRune() (r rune, size int, err os.Error) } インターフェースのメソッドシグネチャも同様に変更されました。

  4. 内部変数およびテストコードでの型変更: 上記のシグネチャ変更に伴い、bufio.go内のReadRuneおよびWriteRuneの実装、そしてbufio_test.go内のテストコードにおいて、rune型の値を格納する変数がint型からrune型に修正されました。例えば、int(b.buf[b.r])のような明示的な型変換が不要になり、直接rune(b.buf[b.r])と記述できるようになりました。また、rune >= 0x80のような比較もr >= 0x80に変更されています。

これらの変更は、Go言語の型システムをより厳密に適用し、コードの意図を明確にするためのものです。rune型を明示的に使用することで、開発者はその変数がUnicodeコードポイントを表していることを一目で理解でき、誤った型での操作を防ぐことができます。

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

src/pkg/bufio/bufio.go

--- a/src/pkg/bufio/bufio.go
+++ b/src/pkg/bufio/bufio.go
@@ -208,7 +208,7 @@ func (b *Reader) UnreadByte() os.Error {
 
 // ReadRune reads a single UTF-8 encoded Unicode character and returns the
 // rune and its size in bytes.
-func (b *Reader) ReadRune() (rune int, size int, err os.Error) {
+func (b *Reader) ReadRune() (r rune, size int, err os.Error) {
 	for b.r+utf8.UTFMax > b.w && !utf8.FullRune(b.buf[b.r:b.w]) && b.err == nil {
 		b.fill()
 	}
@@ -216,14 +216,14 @@ func (b *Reader) ReadRune() (rune int, size int, err os.Error) {
 	if b.r == b.w {
 		return 0, 0, b.readErr()
 	}
-	rune, size = int(b.buf[b.r]), 1
-	if rune >= 0x80 {
-		rune, size = utf8.DecodeRune(b.buf[b.r:b.w])
+	r, size = rune(b.buf[b.r]), 1
+	if r >= 0x80 {
+		r, size = utf8.DecodeRune(b.buf[b.r:b.w])
 	}
 	b.r += size
 	b.lastByte = int(b.buf[b.r-1])
 	b.lastRuneSize = size
-	return rune, size, nil
+	return r, size, nil
 }
 
 // UnreadRune unreads the last rune.  If the most recent read operation on
@@ -497,9 +497,9 @@ func (b *Writer) WriteByte(c byte) os.Error {
 
 // WriteRune writes a single Unicode code point, returning
 // the number of bytes written and any error.
-func (b *Writer) WriteRune(rune int) (size int, err os.Error) {
-	if rune < utf8.RuneSelf {
-		err = b.WriteByte(byte(rune))
+func (b *Writer) WriteRune(r rune) (size int, err os.Error) {
+	if r < utf8.RuneSelf {
+		err = b.WriteByte(byte(r))
 		if err != nil {
 			return 0, err
 		}
@@ -516,10 +516,10 @@ func (b *Writer) WriteRune(rune int) (size int, err os.Error) {
 		if n < utf8.UTFMax {
 			// Can only happen if buffer is silly small.
-			return b.WriteString(string(rune))
+			return b.WriteString(string(r))
 		}
 	}
-	size = utf8.EncodeRune(b.buf[b.n:], rune)
+	size = utf8.EncodeRune(b.buf[b.n:], r)
 	b.n += size
 	return size, nil
 }

src/pkg/io/io.go

--- a/src/pkg/io/io.go
+++ b/src/pkg/io/io.go
@@ -194,7 +194,7 @@ type ByteScanner interface {
 // and returns the rune and its size in bytes. If no character is
 // available, err will be set.
 type RuneReader interface {
-	ReadRune() (rune int, size int, err os.Error)
+	ReadRune() (r rune, size int, err os.Error)
 }
 
 // RuneScanner is the interface that adds the UnreadRune method to the

src/pkg/bufio/bufio_test.go

テストコードも同様に、rune型を使用するように変更されています。

--- a/src/pkg/bufio/bufio_test.go
+++ b/src/pkg/bufio/bufio_test.go
@@ -195,14 +195,14 @@ func readRuneSegments(t *testing.T, segments []string) {
 	want := strings.Join(segments, "")
 	r := NewReader(&StringReader{data: segments})
 	for {
-		rune, _, err := r.ReadRune()
+		r, _, err := r.ReadRune()
 		if err != nil {
 			if err != os.EOF {
 				return
 			}
 			break
 		}
-		got += string(rune)
+		got += string(r)
 	}
 	if got != want {
 		t.Errorf("segments=%v got=%s want=%s", segments, got, want)
@@ -233,24 +233,24 @@ func TestUnreadRune(t *testing.T) {
 	r := NewReader(&StringReader{data: segments})
 	// Normal execution.
 	for {
-		rune, _, err := r.ReadRune()
+		r1, _, err := r.ReadRune()
 		if err != nil {
 			if err != os.EOF {
 				t.Error("unexpected EOF")
 			}
 			break
 		}
-		got += string(rune)
+		got += string(r1)
 		// Put it back and read it again
 		if err = r.UnreadRune(); err != nil {
 			t.Error("unexpected error on UnreadRune:", err)
 		}
-		rune1, _, err := r.ReadRune()
+		r2, _, err := r.ReadRune()
 		if err != nil {
 			t.Error("unexpected error reading after unreading:", err)
 		}
-		if rune != rune1 {
-			t.Errorf("incorrect rune after unread: got %c wanted %c", rune1, rune)
+		if r1 != r2 {
+			t.Errorf("incorrect rune after unread: got %c wanted %c", r2, r1)
 		}
 	}
 	if got != data {
@@ -339,25 +339,25 @@ func TestReadWriteRune(t *testing.T) {
 	w := NewWriter(byteBuf)
 	// Write the runes out using WriteRune
 	buf := make([]byte, utf8.UTFMax)
-	for rune := 0; rune < NRune; rune++ {
-		size := utf8.EncodeRune(buf, rune)
-		nbytes, err := w.WriteRune(rune)
+	for r := rune(0); r < NRune; r++ {
+		size := utf8.EncodeRune(buf, r)
+		nbytes, err := w.WriteRune(r)
 		if err != nil {
-			t.Fatalf("WriteRune(0x%x) error: %s", rune, err)
+			t.Fatalf("WriteRune(0x%x) error: %s", r, err)
 		}
 		if nbytes != size {
-			t.Fatalf("WriteRune(0x%x) expected %d, got %d", rune, size, nbytes)
+			t.Fatalf("WriteRune(0x%x) expected %d, got %d", r, size, nbytes)
 		}
 	}
 	w.Flush()
 
 	r := NewReader(byteBuf)
 	// Read them back with ReadRune
-	for rune := 0; rune < NRune; rune++ {
-		size := utf8.EncodeRune(buf, rune)
+	for r1 := rune(0); r1 < NRune; r1++ {
+		size := utf8.EncodeRune(buf, r1)
 		nr, nbytes, err := r.ReadRune()
-		if nr != rune || nbytes != size || err != nil {
-			t.Fatalf("ReadRune(0x%x) got 0x%x,%d not 0x%x,%d (err=%s)", r, nr, nbytes, r, size, err)
+		if nr != r1 || nbytes != size || err != nil {
+			t.Fatalf("ReadRune(0x%x) got 0x%x,%d not 0x%x,%d (err=%s)", r1, nr, nbytes, r1, size, err)
 		}
 	}
 }

コアとなるコードの解説

bufio.goの変更点

ReadRune関数の変更

// ReadRune reads a single UTF-8 encoded Unicode character and returns the
// rune and its size in bytes.
-func (b *Reader) ReadRune() (rune int, size int, err os.Error) {
+func (b *Reader) ReadRune() (r rune, size int, err os.Error) {
 	// ... (バッファリング処理は省略)
 	if b.r == b.w {
 		return 0, 0, b.readErr()
 	}
-	rune, size = int(b.buf[b.r]), 1
-	if rune >= 0x80 {
-		rune, size = utf8.DecodeRune(b.buf[b.r:b.w])
+	r, size = rune(b.buf[b.r]), 1 // ここでバイトをruneに変換
+	if r >= 0x80 { // ASCII範囲外の文字(マルチバイト文字の可能性)
+		r, size = utf8.DecodeRune(b.buf[b.r:b.w]) // UTF-8デコード
 	}
 	b.r += size
 	b.lastByte = int(b.buf[b.r-1])
 	b.lastRuneSize = size
-	return rune, size, nil
+	return r, size, nil
 }
  • シグネチャの変更: 戻り値の最初の要素の名前がruneからrに変更され、型がintからruneに明示されました。これにより、この関数が返すのがUnicodeコードポイントであることがより明確になります。
  • 変数宣言と代入: 以前はrune, size = int(b.buf[b.r]), 1のようにintに型変換していましたが、変更後はr, size = rune(b.buf[b.r]), 1と直接rune型として扱っています。これは、byteuint8のエイリアス)がruneint32のエイリアス)に暗黙的に変換可能であるためです。
  • 条件分岐: if rune >= 0x80if r >= 0x80に変更されました。これは、ASCII文字(0x00-0x7F)は1バイトで表現され、それ以外のマルチバイト文字は0x80以上の値を持つというUTF-8の特性に基づいています。

WriteRune関数の変更

// WriteRune writes a single Unicode code point, returning
// the number of bytes written and any error.
-func (b *Writer) WriteRune(rune int) (size int, err os.Error) {
-	if rune < utf8.RuneSelf { // utf8.RuneSelfは0x80
-		err = b.WriteByte(byte(rune))
+func (b *Writer) WriteRune(r rune) (size int, err os.Error) {
+	if r < utf8.RuneSelf { // ASCII文字の場合
+		err = b.WriteByte(byte(r)) // 1バイトとして書き込む
 		if err != nil {
 			return 0, err
 		}
 		return 1, nil
 	}
 	// ... (バッファリング処理は省略)
 	if n < utf8.UTFMax {
 		// Can only happen if buffer is silly small.
-		return b.WriteString(string(rune))
+		return b.WriteString(string(r)) // runeをstringに変換して書き込む
 	}
-	size = utf8.EncodeRune(b.buf[b.n:], rune) // runeをUTF-8バイトにエンコード
+	size = utf8.EncodeRune(b.buf[b.n:], r) // runeをUTF-8バイトにエンコード
 	b.n += size
 	return size, nil
 }
  • シグネチャの変更: 引数の名前がruneからrに変更され、型がintからruneに明示されました。
  • 条件分岐と型変換: if rune < utf8.RuneSelfif r < utf8.RuneSelfに変更されました。utf8.RuneSelfはASCII文字の最大値(0x80)であり、これより小さい値は1バイトで表現できるASCII文字であることを示します。
  • WriteStringへの変換: return b.WriteString(string(rune))return b.WriteString(string(r))に変更されました。string(rune)は単一のruneからなる文字列を生成します。
  • utf8.EncodeRuneの引数: utf8.EncodeRune(b.buf[b.n:], rune)utf8.EncodeRune(b.buf[b.n:], r)に変更されました。これは、runeをUTF-8バイトシーケンスにエンコードする関数です。

io.goの変更点

RuneReaderインターフェースの変更

type RuneReader interface {
-	ReadRune() (rune int, size int, err os.Error)
+	ReadRune() (r rune, size int, err os.Error)
}
  • io.RuneReaderインターフェースのReadRuneメソッドのシグネチャも、bufio.Reader.ReadRuneと同様に、戻り値の最初の要素の名前と型が変更されました。これにより、ioパッケージレベルでもrune型が明示的に使用されるようになり、インターフェースの一貫性が保たれます。

これらの変更は、Go言語の型システムをより効果的に活用し、Unicode文字の扱いに関するコードの明確性と堅牢性を向上させるための重要なステップです。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメントおよびブログ記事
  • Go言語のソースコード (特にsrc/pkg/bufio/src/pkg/io/)
  • UnicodeおよびUTF-8に関する一般的な知識
  • コミットメッセージに記載されているGoのコードレビューシステム (Gerrit) のリンク: https://golang.org/cl/5314043 (現在はGoのGerritインスタンスは廃止され、GitHubに移行しています。このリンクは当時のレビューへの参照です。)
  • GitHubのコミットページ: https://github.com/golang/go/commit/9f6d036f333effed712e54c0e087b4be616d2dab