[インデックス 12441] ファイルの概要
このコミットは、Go言語の標準ライブラリであるstrconv
パッケージからunicode
およびstrings
パッケージへの依存関係を削除することを目的としています。これにより、strconv
パッケージがより独立した「near-leaf package」となり、Goの標準ライブラリ全体の依存性グラフが簡素化され、コンパイル時間やバイナリサイズに良い影響を与えることが期待されます。
コミット
commit f91326b7b1de0f699fff4051e41318b7278b4af0
Author: Rob Pike <r@golang.org>
Date: Wed Mar 7 13:50:31 2012 +1100
strconv: remove dependence on unicode and strings
We need a compact, reasonably efficient IsPrint. That adds about 2K of data,
plus a modest amount of code, but now strconv is a near-leaf package.
R=r, bradfitz, adg, rsc, minux.ma
CC=golang-dev
https://golang.org/cl/5756050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f91326b7b1de0f699fff4051e41318b7278b4af0
元コミット内容
strconv
: unicode
とstrings
への依存を削除。
コンパクトで合理的に効率的なIsPrint
が必要。これにより約2KBのデータと少量のコードが追加されるが、strconv
はほぼリーフパッケージとなる。
変更の背景
Go言語の標準ライブラリは、その設計において依存関係の最小化を重視しています。特に、多くの他のパッケージから利用される基盤的なパッケージ(例えばstrconv
)が、さらに多くの依存関係を持つことは、以下のような問題を引き起こす可能性があります。
- コンパイル時間の増加: 依存関係が多いほど、コンパイル時に解決すべきシンボルや読み込むべきコードが増え、全体のコンパイル時間が長くなります。
- バイナリサイズの肥大化: 不要なコードやデータが最終的なバイナリに含まれることで、実行ファイルのサイズが大きくなります。これは特に組み込みシステムやリソースが限られた環境で問題となります。
- 依存性グラフの複雑化: 依存関係が複雑になると、循環参照のリスクが高まったり、特定のパッケージの変更が予期せぬ広範囲に影響を及ぼす可能性が増します。
- 保守性の低下: 依存するパッケージの変更が、依存されるパッケージに影響を与える可能性があり、保守が困難になることがあります。
このコミットの主な動機は、strconv
パッケージがunicode
およびstrings
パッケージに依存している現状を解消することでした。特に、文字列のリテラルをクォートする機能(Quote
, QuoteToASCII
など)において、文字が「表示可能(printable)」であるかを判断するためにunicode.IsPrint
関数が使用されていました。unicode
パッケージは、Unicodeの広範な文字プロパティテーブルを含むため、strconv
がこれに依存すると、strconv
を利用するすべてのアプリケーションが、たとえUnicodeの全機能が必要なくても、その大きなデータテーブルを間接的に取り込むことになります。
このコミットは、strconv
が自身で必要とする最小限のIsPrint
機能と、単純な文字列操作(strings.Contains
に相当する機能)を内部に持つことで、外部パッケージへの依存を断ち切り、strconv
を「near-leaf package」(ほぼ末端のパッケージ、つまり他のパッケージへの依存が非常に少ないパッケージ)にすることを目的としています。これにより、strconv
の独立性が高まり、Go全体のビルドシステムとランタイムの効率が向上します。
前提知識の解説
Go言語のstrconv
パッケージ
strconv
パッケージは、Go言語において基本的なデータ型(整数、浮動小数点数、真偽値など)と文字列との間の変換機能を提供する標準ライブラリです。例えば、文字列を整数に変換するAtoi
やParseInt
、整数を文字列に変換するItoa
やFormatInt
、ブール値を文字列に変換するFormatBool
、文字列をクォートするQuote
などの関数が含まれます。これらの機能は、CLIツール、Webアプリケーションのルーティング、設定ファイルのパースなど、多くのGoプログラムで不可欠です。
Go言語のunicode
パッケージとstrings
パッケージ
unicode
パッケージ: Unicode標準で定義されている文字のプロパティ(カテゴリ、スクリプト、表示可能性など)に関する機能を提供します。例えば、unicode.IsLetter
、unicode.IsDigit
、そしてこのコミットで焦点となるunicode.IsPrint
などがあります。これらの関数は、内部的にUnicodeの文字プロパティテーブルを参照して動作します。strings
パッケージ: 文字列の操作に関する一般的な機能を提供します。例えば、文字列の結合、分割、検索、置換、大文字・小文字変換など、多岐にわたる関数が含まれます。このコミットでは、strings.Contains
関数への依存が問題となりました。
IsPrint
関数の一般的な定義と用途
IsPrint
関数は、与えられた文字(Unicodeコードポイント)が「表示可能(printable)」であるかどうかを判定します。一般的に、表示可能な文字とは、画面や紙に直接出力できる文字であり、制御文字(改行、タブなど)や書式設定文字などは含まれません。プログラミング言語の文字列処理において、特に文字列を引用符で囲んで出力する際や、ユーザー入力のサニタイズを行う際に、表示可能な文字とそうでない文字を区別するために使用されます。
Goにおける「rune」の概念
Go言語では、文字列はUTF-8でエンコードされたバイトのシーケンスとして扱われます。しかし、個々のUnicodeコードポイントを扱う際には「rune
」型が使用されます。rune
はint32
のエイリアスであり、単一のUnicodeコードポイントを表します。これにより、Goは多言語対応や絵文字などの複雑な文字セットを適切に処理できます。
「near-leaf package」の概念
ソフトウェア設計における「リーフパッケージ(leaf package)」とは、他のどのパッケージにも依存しないパッケージを指します。一方、「ニアリーフパッケージ(near-leaf package)」は、他のパッケージへの依存が非常に少ないパッケージを指します。このようなパッケージは、依存性グラフの末端に位置するため、その変更が他の多くのパッケージに影響を与えるリスクが低く、再利用性が高く、テストが容易であるという利点があります。
このコミットでは、strconv
をunicode
やstrings
のような大きなパッケージから切り離すことで、strconv
を「near-leaf package」に近づけ、Go標準ライブラリ全体のモジュール性と効率性を向上させています。
技術的詳細
このコミットの技術的な核心は、strconv
パッケージが外部のunicode
およびstrings
パッケージに依存することなく、必要な機能を内部で提供するように変更された点にあります。
-
unicode.IsPrint
の代替実装:strconv
パッケージ内に、unicode.IsPrint
と同等の機能を提供する独自のIsPrint
関数が実装されました。この実装は、src/pkg/strconv/isprint.go
ファイルに格納されています。- この新しい
IsPrint
関数は、Unicodeの表示可能文字の範囲を定義する静的なデータテーブル(isPrint16
,isNotPrint16
,isPrint32
,isNotPrint32
)を利用します。これらのテーブルは、文字コードの範囲と例外を効率的に検索できるように設計されています。 isPrint32
とisNotPrint32
のデータ構造が変更され、特にisNotPrint32
はuint16
の配列となり、各エントリに0x10000
を加算することで実際のUnicodeコードポイントを表すようになりました。これにより、データサイズが最適化されています。- コミットメッセージにあるように、このデータテーブルの追加により、
strconv
パッケージのバイナリサイズが約2KB増加しますが、これはunicode
パッケージ全体をリンクすることによるサイズ増加と比較すれば非常に小さいものです。
-
データテーブル生成ツールの変更:
src/pkg/strconv/makeisprint.go
は、isprint.go
内のデータテーブルを生成するためのツールです。このツールも、新しいデータ構造に合わせて更新されました。- 特に、
isNotPrint32
の生成ロジックが変更され、uint16
として格納するために0x10000
を減算する処理が追加されました。また、os
パッケージがインポートされ、エラー出力にfmt.Fprintf(os.Stderr, ...)
が使用されるようになりました。 - データサイズ計算のコメントも、新しいデータ構造に合わせて修正されています。
-
strings.Contains
の代替実装:src/pkg/strconv/quote.go
内で使用されていたstrings.Contains
関数は、strconv
パッケージ内に新しく追加されたローカルなcontains
関数に置き換えられました。このcontains
関数は、単純なバイト列の走査によって、指定されたバイトが文字列に含まれるかを判定します。
-
依存関係の削除と定数の置き換え:
src/pkg/strconv/quote.go
から"strings"
と"unicode"
のインポートが削除されました。unicode.IsPrint(r)
の呼び出しは、strconv
パッケージ内で定義されたIsPrint(r)
に置き換えられました。unicode.MaxASCII
はutf8.RuneSelf
に、unicode.MaxRune
はutf8.MaxRune
に置き換えられました。これらはunicode/utf8
パッケージから提供される定数であり、UTF-8エンコーディングにおけるASCII文字の最大値や、有効なUnicodeコードポイントの最大値を表します。unicode/utf8
はstrconv
が既に依存しているパッケージであるため、新たな依存は発生しません。
-
テストの追加:
src/pkg/strconv/quote_test.go
にTestIsPrint
という新しいテスト関数が追加されました。このテストは、strconv
の新しいIsPrint
関数が、既存のunicode.IsPrint
関数と全く同じ結果を返すことを検証します。これにより、機能的な後方互換性が保証されます。
これらの変更により、strconv
パッケージは、その主要な機能である文字列変換とクォート処理を、外部の大きな依存関係なしに実行できるようになりました。これは、Goの標準ライブラリのモジュール性と効率性を高める上で重要なステップです。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
src/pkg/go/build/deps_test.go
:strconv
パッケージの依存関係リストから"unicode"
と"strings"
が削除されました。--- a/src/pkg/go/build/deps_test.go +++ b/src/pkg/go/build/deps_test.go @@ -52,7 +52,7 @@ var pkgDeps = map[string][]string{ "math/rand": {"L0", "math"}, "path": {"L0", "unicode/utf8", "strings"}, "sort": {"math"}, - "strconv": {"L0", "unicode", "unicode/utf8", "math", "strings"}, + "strconv": {"L0", "unicode/utf8", "math"}, "strings": {"L0", "unicode", "unicode/utf8"}, "unicode": {}, "unicode/utf16": {},
-
src/pkg/strconv/isprint.go
:IsPrint
関数の新しい実装と、そのために使用されるデータテーブル(isPrint16
,isNotPrint16
,isPrint32
,isNotPrint32
)が追加・修正されました。特にisPrint32
とisNotPrint32
のデータが大幅に更新されています。--- a/src/pkg/strconv/isprint.go +++ b/src/pkg/strconv/isprint.go @@ -3,7 +3,7 @@ package strconv -// (474+134)*2 + (180+42)*4 = 2104 bytes +// (474+134+42)*2 + (180)*4 = 2020 bytes var isPrint16 = []uint16{ 0x0020, 0x007e, // ... (isPrint16, isNotPrint16 の定義は省略) var isPrint32 = []uint32{ - 0x000020, 0x00007e, - 0x0000a1, 0x000377, // ... (大量のデータ変更) + 0x010000, 0x01004d, + 0x010050, 0x01005d, // ... (大量のデータ変更) } -var isNotPrint32 = []uint32{ - 0x1000c, - 0x10027, // ... (大量のデータ変更) +var isNotPrint32 = []uint16{ // add 0x10000 to each entry + 0x000c, + 0x0027, // ... (大量のデータ変更) } // ... (bsearch16, bsearch32 関数は省略) -func isPrint(r rune) bool { +// TODO: IsPrint is a local implementation of unicode.IsPrint, verified by the tests +// to give the same answer. It allows this package not to depend on unicode, +// and therefore not pull in all the Unicode tables. If the linker were better +// at tossing unused tables, we could get rid of this implementation. +// That would be nice. + +// IsPrint reports whether the rune is defined as printable by Go, with +// the same definition as unicode.IsPrint: letters, numbers, punctuation, +// symbols and ASCII space. +func IsPrint(r rune) bool { + // Fast check for Latin-1 + if r <= 0xFF { + if 0x20 <= r && r <= 0x7E { + // All the ASCII is printable from space through DEL-1. + return true + } + if 0xA1 <= r && r <= 0xFF { + // Similarly for ¡ through ÿ... + return r != 0xAD // ...except for the bizarre soft hyphen. + } + return false + } + // Same algorithm, either on uint16 or uint32 value. // First, find first i such that isPrint[i] >= x. // This is the index of either the start or end of a pair that might span x. var isPrint []uint16 var isNotPrint []uint16 var rr uint32 if r < 0x10000 { isPrint = isPrint16 isNotPrint = isNotPrint16 rr = uint32(r) } else { isPrint = isPrint32 isNotPrint = isNotPrint32 rr = uint32(r) } i := bsearch16(isPrint, uint16(rr)) if i >= len(isPrint) || rr < uint32(isPrint[i&^1]) || uint32(isPrint[i|1]) < rr { return false } - j := bsearch32(isNotPrint, rr) - return j >= len(isNotPrint) || isNotPrint[j] != rr + if r >= 0x20000 { // 0x20000以上の文字はすべて表示可能とみなす + return true + } + r -= 0x10000 // isNotPrint32 は 0x10000 を引いた値で格納されている + j := bsearch16(isNotPrint, uint16(r)) + return j >= len(isNotPrint) || isNotPrint[j] != uint16(r) }
-
src/pkg/strconv/makeisprint.go
: データサイズ計算のコメントと、isNotPrint32
の生成ロジックが変更されました。また、エラー出力にos.Stderr
が使用されるようになりました。--- a/src/pkg/strconv/makeisprint.go +++ b/src/pkg/strconv/makeisprint.go @@ -9,6 +9,7 @@ package main import ( "fmt" + "os" "unicode" ) @@ -116,8 +117,8 @@ func main() { for i := rune(0); i <= unicode.MaxRune; i++ { if isPrint(i) != unicode.IsPrint(i) { - fmt.Printf("%U: isPrint=%v, want %v\n", i, isPrint(i), unicode.IsPrint(i)) - break + fmt.Fprintf(os.Stderr, "%U: isPrint=%v, want %v\n", i, isPrint(i), unicode.IsPrint(i)) + return } } @@ -125,11 +126,11 @@ func main() { fmt.Printf("// go run makeisprint.go >x && mv x isprint.go\\n\\n") fmt.Printf("package strconv\\n\\n") - fmt.Printf("// (%d+%d)*2 + (%d+%d)*4 = %d bytes\\n\\n", - len(range16), len(except16), - len(range32), len(except32), - (len(range16)+len(except16))*2+ - (len(range32)+len(except32))*4) + fmt.Printf("// (%d+%d+%d)*2 + (%d)*4 = %d bytes\\n\\n", + len(range16), len(except16), len(except32), + len(range32), + (len(range16)+len(except16)+len(except32))*2+ + (len(range32))*4) fmt.Printf("var isPrint16 = []uint16{\\n") for i := 0; i < len(range16); i += 2 { @@ -145,13 +146,17 @@ func main() { fmt.Printf("var isPrint32 = []uint32{\\n") for i := 0; i < len(range32); i += 2 { - fmt.Printf("\t%#06x, %#06x,\n", range16[i], range16[i+1]) + fmt.Printf("\t%#06x, %#06x,\n", range32[i], range32[i+1]) } fmt.Printf("}\\n\\n") - fmt.Printf("var isNotPrint32 = []uint32{\\n") + fmt.Printf("var isNotPrint32 = []uint16{ // add 0x10000 to each entry\\n") for _, r := range except32 { - fmt.Printf("\t%#04x,\\n", r) + if r >= 0x20000 { + fmt.Fprintf(os.Stderr, "%U too big for isNotPrint32\\n", r) + return + } + fmt.Printf("\t%#04x,\\n", r-0x10000) } fmt.Printf("}\\n") }
-
src/pkg/strconv/quote.go
:"strings"
と"unicode"
のインポートが削除され、unicode.IsPrint
の呼び出しがローカルのIsPrint
に置き換えられました。また、strings.Contains
の代わりにローカルのcontains
関数が追加・使用されています。--- a/src/pkg/strconv/quote.go +++ b/src/pkg/strconv/quote.go @@ -5,8 +5,6 @@ package strconv import ( - "strings" - "unicode" "unicode/utf8" ) @@ -34,11 +32,11 @@ func quoteWith(s string, quote byte, ASCIIonly bool) string { continue } if ASCIIonly { - if r <= unicode.MaxASCII && unicode.IsPrint(r) { + if r < utf8.RuneSelf && IsPrint(r) { buf = append(buf, byte(r)) continue } - } else if unicode.IsPrint(r) { + } else if IsPrint(r) { n := utf8.EncodeRune(runeTmp[:], r) buf = append(buf, runeTmp[:n]...) continue @@ -64,7 +62,7 @@ func quoteWith(s string, quote byte, ASCIIonly bool) string { buf = append(buf, `\x`...) buf = append(buf, lowerhex[s[0]>>4]) buf = append(buf, lowerhex[s[0]&0xF]) - case r > unicode.MaxRune: + case r > utf8.MaxRune: r = 0xFFFD fallthrough case r < 0x10000: @@ -88,7 +86,7 @@ func quoteWith(s string, quote byte, ASCIIonly bool) string { // Quote returns a double-quoted Go string literal representing s. The // returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for // control characters and non-printable characters as defined by -// unicode.IsPrint. +// IsPrint. func Quote(s string) string { return quoteWith(s, '"', false) } @@ -101,8 +99,7 @@ func AppendQuote(dst []byte, s string) []byte { // QuoteToASCII returns a double-quoted Go string literal representing s. // The returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for -// non-ASCII characters and non-printable characters as defined by -// unicode.IsPrint. +// non-ASCII characters and non-printable characters as defined by IsPrint. func QuoteToASCII(s string) string { return quoteWith(s, '"', true) } @@ -115,8 +111,7 @@ func AppendQuoteToASCII(dst []byte, s string) []byte { // QuoteRune returns a single-quoted Go character literal representing the // rune. The returned string uses Go escape sequences (\t, \n, \xFF, \u0100) // for control characters and non-printable characters as defined by -// unicode.IsPrint. +// IsPrint. func QuoteRune(r rune) string { // TODO: avoid the allocation here. return quoteWith(string(r), '\'', false) @@ -131,7 +126,7 @@ func AppendQuoteRune(dst []byte, r rune) []byte { // QuoteRuneToASCII returns a single-quoted Go character literal representing // the rune. The returned string uses Go escape sequences (\t, \n, \xFF, // \u0100) for non-ASCII characters and non-printable characters as defined -// by unicode.IsPrint. +// by IsPrint. func QuoteRuneToASCII(r rune) string { // TODO: avoid the allocation here. return quoteWith(string(r), '\'', true) @@ -246,7 +241,7 @@ func UnquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, value = v break } - if v > unicode.MaxRune { + if v > utf8.MaxRune { err = ErrSyntax return } @@ -305,7 +300,7 @@ func Unquote(s string) (t string, err error) { s = s[1 : n-1] if quote == '`' { - if strings.Contains(s, "`") { + if contains(s, '`') { return "", ErrSyntax } return s, nil @@ -313,12 +308,12 @@ func Unquote(s string) (t string, err error) { if quote != '"' && quote != '\'' { return "", ErrSyntax } - if strings.Index(s, "\n") >= 0 { + if contains(s, '\n') { return "", ErrSyntax } // Is it trivial? Avoid allocation. - if strings.Index(s, `\`) < 0 && strings.IndexRune(s, rune(quote)) < 0 { + if !contains(s, '\\') && !contains(s, quote) { switch quote { case '"': return s, nil @@ -352,6 +347,16 @@ func Unquote(s string) (t string, err error) { return string(buf), nil } +// contains reports whether the string contains the byte c. +func contains(s string, c byte) bool { + for i := 0; i < len(s); i++ { + if s[i] == c { + return true + } + } + return false +} + // bsearch16 returns the smallest i such that a[i] >= x. // If there is no such i, bsearch16 returns len(a). func bsearch16(a []uint16, x uint16) int {
-
src/pkg/strconv/quote_test.go
:strconv
の新しいIsPrint
関数がunicode.IsPrint
と同じ結果を返すことを検証するテストが追加されました。--- a/src/pkg/strconv/quote_test.go +++ b/src/pkg/strconv/quote_test.go @@ -7,8 +7,23 @@ package strconv_test import ( . "strconv" "testing" + "unicode" ) +// Verify that our isPrint agrees with unicode.IsPrint +func TestIsPrint(t *testing.T) { + n := 0 + for r := rune(0); r <= unicode.MaxRune; r++ { + if IsPrint(r) != unicode.IsPrint(r) { + t.Errorf("IsPrint(%U)=%t incorrect", r, IsPrint(r)) + n++ + if n > 10 { + return + } + } + } +} + type quoteTest struct { in string out string
コアとなるコードの解説
src/pkg/go/build/deps_test.go
このファイルはGoのビルドシステムがパッケージの依存関係をテストするために使用するものです。strconv
の依存リストから"unicode"
と"strings"
が削除されたことは、ビルドシステムがこれらのパッケージをstrconv
の直接的な依存として認識しなくなったことを意味します。これは、このコミットの目的である依存関係の削減が、ビルドレベルで反映されたことを示しています。
src/pkg/strconv/isprint.go
このファイルは、strconv
パッケージが独自に実装したIsPrint
関数の定義と、その機能に必要なデータテーブルを含んでいます。
-
データテーブル (
isPrint16
,isNotPrint16
,isPrint32
,isNotPrint32
): これらの配列は、Unicodeの表示可能文字の範囲と、その例外(表示可能ではないが、範囲内に含まれる文字)を効率的に表現するために使用されます。isPrint16
とisNotPrint16
はuint16
(0x0000-0xFFFFの範囲の文字、つまりBMP: Basic Multilingual Plane)の文字を扱います。isPrint32
はuint32
(0x10000以上の文字、つまりサロゲートペアや追加多言語面など)の文字の範囲を扱います。isNotPrint32
は、isPrint32
の範囲内で表示可能ではない文字の例外をuint16
として格納します。この際、実際のUnicodeコードポイントから0x10000
を引いた値が格納されます。これは、0x10000
以上の文字を扱う際に、データサイズを削減するための工夫です。 -
IsPrint(r rune) bool
関数: この関数は、与えられたrune
(Unicodeコードポイント)が表示可能かどうかを判定します。- Latin-1 (0x00-0xFF) の高速チェック: まず、
rune
がLatin-1の範囲内にある場合、ASCII文字(0x20-0x7E)と一部の拡張Latin-1文字(0xA1-0xFF、ただしソフトハイフン0xADを除く)を高速に判定します。これは、これらの文字が非常に頻繁に現れるため、パフォーマンスを向上させるための最適化です。 - データテーブルによる検索: Latin-1の範囲外の文字については、
bsearch16
やbsearch32
といった二分探索関数を使用して、適切なデータテーブル(isPrint16
またはisPrint32
)を検索します。これにより、文字がどの表示可能範囲に属するかを効率的に特定します。 - 例外のチェック: 最後に、文字が表示可能範囲内に含まれる場合でも、
isNotPrint16
またはisNotPrint32
テーブルにその文字が例外として登録されていないかを確認します。例外リストに存在しない場合のみ、その文字は表示可能と判定されます。 - 0x20000以上の文字の扱い:
if r >= 0x20000 { return true }
という行は、0x20000
以上のUnicodeコードポイント(主に絵文字や特殊記号など)は、このIsPrint
の実装ではすべて表示可能とみなすことを示しています。これは、これらの文字の表示可能性を詳細に判定するためのデータテーブルをさらに大きくすることを避けるためのトレードオフと考えられます。
- Latin-1 (0x00-0xFF) の高速チェック: まず、
このIsPrint
の実装は、unicode
パッケージの完全なUnicodeプロパティテーブルを読み込むことなく、strconv
が必要とする表示可能性の判定を効率的に行うことを可能にしています。
src/pkg/strconv/makeisprint.go
このファイルは、isprint.go
内のデータテーブルを自動生成するためのGoプログラムです。
- データサイズ計算の更新: コメント行のデータサイズ計算式が、新しいデータ構造(
isNotPrint32
がuint16
になったこと)に合わせて修正されています。これにより、生成されるデータが実際にどの程度のメモリを消費するかが正確に示されます。 isNotPrint32
の生成ロジック:isNotPrint32
配列に格納される値は、元のUnicodeコードポイントから0x10000
を引いたuint16
値として出力されるようになりました。これは、isprint.go
のIsPrint
関数がこのオフセットを考慮して検索を行うためです。また、0x20000
以上の文字がisNotPrint32
に格納されようとした場合にエラーを出力し、プログラムを終了するチェックが追加されました。これは、isNotPrint32
がuint16
で表現できる範囲(最大0xFFFF
)に収まるようにするためのガードです。- エラー出力の変更:
fmt.Printf
からfmt.Fprintf(os.Stderr, ...)
に変更されたことで、生成されたデータが標準出力に書き込まれる際に、エラーメッセージが標準エラー出力に分離されるようになりました。これにより、スクリプトの出力がクリーンに保たれます。
src/pkg/strconv/quote.go
このファイルは、文字列やruneをGoの文字列リテラル形式にクォートする機能を提供します。
- インポートの削除:
import ("strings", "unicode")
が削除され、strconv
がこれらのパッケージに依存しなくなりました。 unicode.IsPrint
からIsPrint
への置き換え:quoteWith
関数内で、文字の表示可能性を判定するためにunicode.IsPrint(r)
が呼び出されていた箇所が、strconv
パッケージ内で新しく定義されたIsPrint(r)
に置き換えられました。これにより、外部のunicode
パッケージへの依存が解消されます。unicode.MaxASCII
からutf8.RuneSelf
への置き換え: ASCII文字の範囲を判定するために使用されていたunicode.MaxASCII
がutf8.RuneSelf
に置き換えられました。utf8.RuneSelf
は、UTF-8エンコーディングにおいて、1バイトで表現できる最大のrune(つまりASCII文字の範囲)を表す定数です。これはunicode/utf8
パッケージがstrconv
の既存の依存であるため、新たな依存は発生しません。unicode.MaxRune
からutf8.MaxRune
への置き換え: 有効なUnicodeコードポイントの最大値を表すunicode.MaxRune
がutf8.MaxRune
に置き換えられました。utf8.MaxRune
は、UTF-8でエンコード可能な最大のUnicodeコードポイントを表します。これも既存の依存パッケージからの定数利用です。strings.Contains
の代替 (contains
関数):Unquote
関数内で文字列が特定のバイトを含むかをチェックするためにstrings.Contains
が使用されていましたが、これがstrconv
パッケージ内に新しく定義されたローカルなcontains
関数に置き換えられました。このcontains
関数は、単純なループで文字列を走査し、指定されたバイトが見つかればtrue
を返します。これにより、strings
パッケージへの依存が解消されます。
src/pkg/strconv/quote_test.go
このファイルはstrconv
パッケージのテストを含んでいます。
TestIsPrint
の追加: 新しく追加されたTestIsPrint
関数は、strconv
のIsPrint
関数が、Goの標準unicode
パッケージのunicode.IsPrint
関数と完全に同じ結果を返すことを検証します。これは、strconv
が独自のIsPrint
実装を持つようになったにもかかわらず、その機能が既存の期待される動作と一致していることを保証するための重要なテストです。このテストは、unicode.MaxRune
までのすべてのUnicodeコードポイントに対してIsPrint
を呼び出し、結果を比較します。最初の10個の不一致が見つかった時点でテストを終了し、エラーを報告します。
これらの変更は、strconv
パッケージの自己完結性を高め、Goの標準ライブラリ全体の依存関係を簡素化するというコミットの目標を達成しています。
関連リンク
- Go言語公式ドキュメント: https://go.dev/
strconv
パッケージドキュメント: https://pkg.go.dev/strconvunicode
パッケージドキュメント: https://pkg.go.dev/unicodestrings
パッケージドキュメント: https://pkg.go.dev/stringsunicode/utf8
パッケージドキュメント: https://pkg.go.dev/unicode/utf8- Go Code Review Comments - Package Names: https://go.dev/doc/effective_go#package-names (パッケージ設計の原則について言及)
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Goのコードレビューシステム (Gerrit): https://go.googlesource.com/go/+/refs/heads/master (コミットメッセージに記載されている
https://golang.org/cl/5756050
はGerritの変更リストへのリンクです) - Unicode標準: https://www.unicode.org/
- UTF-8に関する情報: https://ja.wikipedia.org/wiki/UTF-8
- Go言語におけるパッケージの依存関係と設計に関する一般的な議論(Stack Overflow, ブログ記事など)
- "Go: What is a leaf package?": https://stackoverflow.com/questions/30000000/go-what-is-a-leaf-package (Stack Overflowの関連質問)
- "Go's Clean Architecture": https://medium.com/@benbjohnson/go-clean-architecture-c4d7ce60b955 (Goにおけるアーキテクチャと依存関係に関する一般的な記事)