[インデックス 10099] ファイルの概要
このコミットは、Go言語の標準ライブラリであるfmt
パッケージにおいて、文字の表現にint
型ではなくrune
型を使用するように変更したものです。これにより、Unicode文字の扱いがより正確かつ自然になります。Formatter
およびScanner
インターフェースの定義も変更され、既存のクライアントコードに影響を与える可能性があるため、govet
によるチェックが推奨されています。
コミット
- コミットハッシュ:
4e4eca261817c80c77ebe0b8522df8b0746cf10b
- Author: Russ Cox rsc@golang.org
- Date: Tue Oct 25 22:21:33 2011 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4e4eca261817c80c77ebe0b8522df8b0746cf10b
元コミット内容
fmt: use rune
Lots of internal edits.
Formatter and Scanner interfaces change
(clients to be checked by govet).
R=r
CC=golang-dev
https://golang.org/cl/5305045
変更の背景
Go言語は設計当初からUnicodeを強く意識しており、文字列はUTF-8でエンコードされたバイト列として扱われます。しかし、個々の文字(Unicodeコードポイント)を扱う際には、初期のGoではint
型が使われることがありました。これは、Goのint
型が少なくとも32ビット幅を持つことが保証されており、Unicodeのすべてのコードポイント(U+0000からU+10FFFFまで)を表現できるためです。
しかし、int
型は汎用的な整数型であり、それが文字を表すのか、単なる数値を表すのかがコードの可読性や意図の明確さの点で曖昧になるという問題がありました。Go言語には、Unicodeコードポイントを明示的に表すための組み込み型としてrune
型が存在します。rune
はint32
のエイリアスであり、その目的は「Unicodeコードポイント」を表現することに特化しています。
このコミットの背景には、fmt
パッケージのような低レベルのテキスト処理を行う部分において、文字を扱う際にはその意図を明確にし、コードの堅牢性を高める目的があったと考えられます。int
をrune
に置き換えることで、開発者はその変数がUnicodeコードポイントを扱っていることを一目で理解できるようになり、誤用を防ぐ効果も期待できます。また、Go言語全体のAPI設計の一貫性を保つ上でも重要な変更でした。
前提知識の解説
Go言語におけるrune
型
Go言語において、rune
はUnicodeコードポイントを表す組み込み型です。これはint32
のエイリアスとして定義されており、Goの文字列がUTF-8でエンコードされているのに対し、rune
は個々のUnicode文字(コードポイント)を表現するために使用されます。
string
: UTF-8でエンコードされたバイト列のシーケンス。文字列の長さはバイト数で決まり、文字数とは異なる場合がある。rune
: 1つのUnicodeコードポイントを表す。int32
型であり、すべてのUnicode文字を表現できる。
例えば、日本語の「あ」はUTF-8では3バイトですが、rune
としては1つのコードポイントとして扱われます。
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "こんにちは"
fmt.Printf("String: %s, Bytes: %d\n", s, len(s)) // String: こんにちは, Bytes: 15
// runeとしてイテレート
for i, r := range s {
fmt.Printf("Index: %d, Rune: %c, Unicode: %U, Size: %d\n", i, r, r, utf8.RuneLen(r))
}
// 出力例:
// Index: 0, Rune: こ, Unicode: U+3053, Size: 3
// Index: 3, Rune: ん, Unicode: U+3093, Size: 3
// Index: 6, Rune: に, Unicode: U+306B, Size: 3
// Index: 9, Rune: ち, Unicode: U+3061, Size: 3
// Index: 12, Rune: は, Unicode: U+306F, Size: 3
}
fmt
パッケージ
fmt
パッケージは、Go言語におけるフォーマットI/Oを実装するパッケージです。C言語のprintf
やscanf
に似た機能を提供し、Goのデータ型を文字列に変換したり、文字列からデータ型に変換したりする機能を提供します。fmt.Printf
、fmt.Sprintf
、fmt.Scan
などが含まれます。
Formatter
インターフェースとScanner
インターフェース
fmt
パッケージには、カスタムのフォーマットやスキャン動作を定義するためのインターフェースが用意されています。
-
Formatter
インターフェース:type Formatter interface { Format(f State, c int) }
このインターフェースを実装する型は、fmt.Printf
などのフォーマット関数でどのように表示されるかを制御できます。変更前はc int
でしたが、このコミットでc rune
に変更されました。c
はフォーマット動詞(例:%c
,%d
,%s
など)を表します。 -
Scanner
インターフェース:type Scanner interface { Scan(state ScanState, verb int) os.Error }
このインターフェースを実装する型は、fmt.Scan
などのスキャン関数でどのように入力が読み取られるかを制御できます。変更前はverb int
でしたが、このコミットでverb rune
に変更されました。verb
はスキャン動詞(例:%c
,%d
,%s
など)を表します。
govet
ツール
govet
はGo言語の静的解析ツールで、Goのソースコードを検査し、疑わしい構成要素(バグの可能性のあるコード)を報告します。このコミットのように、インターフェースのシグネチャが変更された場合、govet
は古いインターフェースを実装しているが新しいインターフェースに適合していないコードを検出するのに役立ちます。
技術的詳細
このコミットの主要な技術的変更は、fmt
パッケージ内で文字(特にフォーマット動詞やスキャン動詞)を表現するために使用されていたint
型を、よりセマンティックなrune
型に置き換えたことです。
具体的には、以下の点が変更されました。
-
Formatter
インターフェースの変更:Format(f State, c int)
からFormat(f State, c rune)
へと変更されました。これにより、カスタムフォーマッタを実装する際に、フォーマット動詞が明確にrune
として扱われるようになります。 -
Scanner
インターフェースの変更:Scan(state ScanState, verb int) os.Error
からScan(state ScanState, verb rune) os.Error
へと変更されました。これにより、カスタムスキャナを実装する際に、スキャン動詞が明確にrune
として扱われるようになります。 -
内部関数のシグネチャ変更:
fmt
パッケージ内の多くのプライベート関数やヘルパー関数で、文字や動詞を表す引数の型がint
からrune
に変更されました。例えば、add(c int)
がadd(c rune)
に、badVerb(verb int)
がbadVerb(verb rune)
に、fmtInt64(v int64, verb int)
がfmtInt64(v int64, verb rune)
に変更されています。 -
ss
構造体のフィールド変更:src/pkg/fmt/scan.go
内のss
構造体(スキャナの状態を保持する)で、peekRune int
とprevRune int
がそれぞれpeekRune rune
とprevRune rune
に変更されました。これは、読み取り中のルックアヘッド文字や直前の文字をrune
として保持するためです。 -
型変換の明示化: 一部の箇所では、
int(a)
のような明示的な型変換がrune(a)
に変更されています。これは、数値がUnicodeコードポイントとして扱われることをより明確にするためです。例えば、unicode.IsPrint(int(a))
がunicode.IsPrint(rune(a))
に変更されています。
これらの変更は、Go言語の型システムをより厳密に適用し、コードの意図を明確にするためのものです。int
とrune
は基底型が同じint32
であるため、多くの場合はコンパイルエラーにはなりませんが、インターフェースのシグネチャ変更は互換性を破る変更であり、既存のコードが新しいインターフェースに適合するように修正する必要があることを意味します。そのため、コミットメッセージに「(clients to be checked by govet)」と明記されています。
コアとなるコードの変更箇所
このコミットでは、以下の5つのファイルが変更されています。
src/pkg/fmt/fmt_test.go
src/pkg/fmt/format.go
src/pkg/fmt/print.go
src/pkg/fmt/scan.go
src/pkg/fmt/scan_test.go
変更の概要は以下の通りです。
fmt_test.go
:Formatter
インターフェースを実装するテスト用の型(F
,flagPrinter
,PanicF
)のFormat
メソッドのシグネチャがc int
からc rune
に変更されています。format.go
:fmt
構造体の内部メソッド(integer
,fmt_c64
,fmt_c128
)で、文字や動詞を表す引数の型がint
からrune
に変更されています。また、unicode.IsPrint
やutf8.RuneLen
、utf8.EncodeRune
への引数もint(a)
からrune(a)
に変更されています。print.go
:State
インターフェースとFormatter
インターフェースの定義が変更され、int
がrune
に置き換えられています。また、pp
構造体の多くのメソッド(add
,badVerb
,fmtBool
,fmtInt64
,fmtUint64
,fmtFloat32
,fmtFloat64
,fmtComplex64
,fmtComplex128
,fmtString
,fmtBytes
,fmtPointer
,catchPanic
,handleMethods
,printField
,printValue
,printReflectValue
)の引数verb
やc
の型がint
からrune
に変更されています。fmtC
関数では、int64
からrune
への変換が明示的に行われています。scan.go
:ScanState
インターフェースとScanner
インターフェースの定義が変更され、int
がrune
に置き換えられています。ss
構造体のpeekRune
とprevRune
フィールドの型がint
からrune
に変更されています。ReadRune
、getRune
、mustReadRune
、Token
、notSpace
、readRune
、skipSpace
、token
、consume
、peek
、notEOF
、okVerb
、scanBool
、getBase
、scanRune
、scanInt
、scanUint
、scanComplex
、convertString
、quotedString
、hexDigit
、scanOne
、doScan
などの多くの関数やメソッドで、文字や動詞を表す引数や変数の型がint
からrune
に変更されています。scan_test.go
:Scanner
インターフェースを実装するテスト用の型(Xs
,IntString
,TwoLines
,RecursiveInt
)のScan
メソッドのシグネチャがverb int
からverb rune
に変更されています。また、TwoLines
のchars
スライスが[]int
から[]rune
に変更されています。
全体として、fmt
パッケージのフォーマットおよびスキャン処理において、文字やフォーマット/スキャン動詞を扱うすべての箇所でint
型がrune
型に置き換えられています。
コアとなるコードの解説
以下に、変更の代表的な箇所をいくつか抜粋して解説します。
src/pkg/fmt/fmt_test.go
--- a/src/pkg/fmt/fmt_test.go
+++ b/src/pkg/fmt/fmt_test.go
@@ -73,7 +73,7 @@ type C struct {
type F int
-func (f F) Format(s State, c int) {
+func (f F) Format(s State, c rune) {
Fprintf(s, "<%c=F(%d)>", c, int(f))
}
ここでは、Formatter
インターフェースを実装するF
型のFormat
メソッドのシグネチャが変更されています。引数c
がint
からrune
になりました。これにより、フォーマット動詞がrune
として渡されることが明確になります。
src/pkg/fmt/format.go
--- a/src/pkg/fmt/format.go
+++ b/src/pkg/fmt/format.go
@@ -242,8 +242,8 @@ func (f *fmt) integer(a int64, base uint64, signedness bool, digits string) {
}
// If we want a quoted char for %#U, move the data up to make room.
-\tif f.unicode && f.uniQuote && a >= 0 && a <= unicode.MaxRune && unicode.IsPrint(int(a)) {
-\t\truneWidth := utf8.RuneLen(int(a))\n+\tif f.unicode && f.uniQuote && a >= 0 && a <= unicode.MaxRune && unicode.IsPrint(rune(a)) {
+\t\truneWidth := utf8.RuneLen(rune(a))\n \t\twidth := 1 + 1 + runeWidth + 1 // space, quote, rune, quote
\t\tcopy(buf[i-width:], buf[i:]) // guaranteed to have enough room.
\t\ti -= width
@@ -253,7 +253,7 @@ func (f *fmt) integer(a int64, base uint64, signedness bool, digits string) {
\t\tj++
\t\tbuf[j] = '\''
\t\tj++
-\t\tutf8.EncodeRune(buf[j:], int(a))\n+\t\tutf8.EncodeRune(buf[j:], rune(a))\n \t\tj += runeWidth
\t\tbuf[j] = '\''
\t}
@@ -400,7 +400,7 @@ func (f *fmt) fmt_G32(v float32) { f.plusSpace(strconv.Ftoa32(v, 'G', doPrec(f,\
func (f *fmt) fmt_fb32(v float32) { f.padString(strconv.Ftoa32(v, 'b', 0)) }
// fmt_c64 formats a complex64 according to the verb.
-func (f *fmt) fmt_c64(v complex64, verb int) {
+func (f *fmt) fmt_c64(v complex64, verb rune) {
f.buf.WriteByte('(')
r := real(v)
for i := 0; ; i++ {
unicode.IsPrint
やutf8.RuneLen
、utf8.EncodeRune
といったUnicode関連の関数に渡す引数が、int(a)
からrune(a)
に明示的に変更されています。これは、a
が数値としてではなく、Unicodeコードポイントとして扱われるべきであることを明確に示しています。また、fmt_c64
などの内部メソッドのverb
引数もint
からrune
に変更されています。
src/pkg/fmt/print.go
--- a/src/pkg/fmt/print.go
+++ b/src/pkg/fmt/print.go
@@ -51,7 +51,7 @@ type State interface {
// The implementation of Format may call Sprintf or Fprintf(f) etc.
// to generate its output.
type Formatter interface {
-\tFormat(f State, c int)\n+\tFormat(f State, c rune)\n }\n \n // Stringer is implemented by any value that has a String method,
@@ -159,7 +159,7 @@ func (p *pp) Flag(b int) bool {
return false
}
-func (p *pp) add(c int) {
+func (p *pp) add(c rune) {
p.buf.WriteRune(c)
}
Formatter
インターフェースの定義自体が変更されています。これは、このインターフェースを実装するすべての型に影響を与える重要な変更です。また、pp
構造体のadd
メソッドのように、文字をバッファに追加する内部関数もint
からrune
を受け取るように変更されています。
src/pkg/fmt/scan.go
--- a/src/pkg/fmt/scan.go
+++ b/src/pkg/fmt/scan.go
@@ -32,7 +32,7 @@ type ScanState interface {
// If invoked during Scanln, Fscanln, or Sscanln, ReadRune() will
// return EOF after returning the first '\n' or when reading beyond
// the specified width.
-\tReadRune() (rune int, size int, err os.Error)\n+\tReadRune() (r rune, size int, err os.Error)\n \t// UnreadRune causes the next call to ReadRune to return the same rune.
\tUnreadRune() os.Error
\t// SkipSpace skips space in the input. Newlines are treated as space
@@ -47,7 +47,7 @@ type ScanState interface {
// EOF. The returned slice points to shared data that may be overwritten
// by the next call to Token, a call to a Scan function using the ScanState
// as input, or when the calling Scan method returns.
-\tToken(skipSpace bool, f func(int) bool) (token []byte, err os.Error)\n+\tToken(skipSpace bool, f func(rune) bool) (token []byte, err os.Error)\n \t// Width returns the value of the width option and whether it has been set.
\t// The unit is Unicode code points.
\tWidth() (wid int, ok bool)
@@ -62,7 +62,7 @@ type ScanState interface {
// receiver, which must be a pointer to be useful. The Scan method is called
// for any argument to Scan, Scanf, or Scanln that implements it.
type Scanner interface {
-\tScan(state ScanState, verb int) os.Error\n+\tScan(state ScanState, verb rune) os.Error\n }\n \n // Scan scans text read from standard input, storing successive
@@ -149,8 +149,8 @@ const eof = -1
type ss struct {
rr io.RuneReader // where to read input
buf bytes.Buffer // token accumulator
-\tpeekRune int // one-rune lookahead\n-\tprevRune int // last rune returned by ReadRune\n+\tpeekRune rune // one-rune lookahead\n+\tprevRune rune // last rune returned by ReadRune\n count int // runes consumed so far.
atEOF bool // already read EOF
ssave
ScanState
とScanner
インターフェースの定義が変更され、int
がrune
に置き換えられています。特にReadRune
メソッドの戻り値の型名がrune int
からr rune
に変更されている点に注目です。これは、戻り値がrune
型であることをより明確にするための変更です。また、ss
構造体の内部状態を保持するpeekRune
とprevRune
もrune
型に変更されています。
これらの変更は、Go言語のfmt
パッケージがUnicode文字をより正確かつ意図的に扱うように進化していることを示しています。
関連リンク
参考にした情報源リンク
- Go言語の公式ドキュメント(
fmt
パッケージ、unicode
パッケージ、utf8
パッケージに関する情報) - Go言語の
rune
型に関する解説記事 - Go言語のインターフェースに関する解説記事
- Go言語の
govet
ツールに関する情報