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

[インデックス 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: unicodestringsへの依存を削除。 コンパクトで合理的に効率的なIsPrintが必要。これにより約2KBのデータと少量のコードが追加されるが、strconvはほぼリーフパッケージとなる。

変更の背景

Go言語の標準ライブラリは、その設計において依存関係の最小化を重視しています。特に、多くの他のパッケージから利用される基盤的なパッケージ(例えばstrconv)が、さらに多くの依存関係を持つことは、以下のような問題を引き起こす可能性があります。

  1. コンパイル時間の増加: 依存関係が多いほど、コンパイル時に解決すべきシンボルや読み込むべきコードが増え、全体のコンパイル時間が長くなります。
  2. バイナリサイズの肥大化: 不要なコードやデータが最終的なバイナリに含まれることで、実行ファイルのサイズが大きくなります。これは特に組み込みシステムやリソースが限られた環境で問題となります。
  3. 依存性グラフの複雑化: 依存関係が複雑になると、循環参照のリスクが高まったり、特定のパッケージの変更が予期せぬ広範囲に影響を及ぼす可能性が増します。
  4. 保守性の低下: 依存するパッケージの変更が、依存されるパッケージに影響を与える可能性があり、保守が困難になることがあります。

このコミットの主な動機は、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言語において基本的なデータ型(整数、浮動小数点数、真偽値など)と文字列との間の変換機能を提供する標準ライブラリです。例えば、文字列を整数に変換するAtoiParseInt、整数を文字列に変換するItoaFormatInt、ブール値を文字列に変換するFormatBool、文字列をクォートするQuoteなどの関数が含まれます。これらの機能は、CLIツール、Webアプリケーションのルーティング、設定ファイルのパースなど、多くのGoプログラムで不可欠です。

Go言語のunicodeパッケージとstringsパッケージ

  • unicodeパッケージ: Unicode標準で定義されている文字のプロパティ(カテゴリ、スクリプト、表示可能性など)に関する機能を提供します。例えば、unicode.IsLetterunicode.IsDigit、そしてこのコミットで焦点となるunicode.IsPrintなどがあります。これらの関数は、内部的にUnicodeの文字プロパティテーブルを参照して動作します。
  • stringsパッケージ: 文字列の操作に関する一般的な機能を提供します。例えば、文字列の結合、分割、検索、置換、大文字・小文字変換など、多岐にわたる関数が含まれます。このコミットでは、strings.Contains関数への依存が問題となりました。

IsPrint関数の一般的な定義と用途

IsPrint関数は、与えられた文字(Unicodeコードポイント)が「表示可能(printable)」であるかどうかを判定します。一般的に、表示可能な文字とは、画面や紙に直接出力できる文字であり、制御文字(改行、タブなど)や書式設定文字などは含まれません。プログラミング言語の文字列処理において、特に文字列を引用符で囲んで出力する際や、ユーザー入力のサニタイズを行う際に、表示可能な文字とそうでない文字を区別するために使用されます。

Goにおける「rune」の概念

Go言語では、文字列はUTF-8でエンコードされたバイトのシーケンスとして扱われます。しかし、個々のUnicodeコードポイントを扱う際には「rune」型が使用されます。runeint32のエイリアスであり、単一のUnicodeコードポイントを表します。これにより、Goは多言語対応や絵文字などの複雑な文字セットを適切に処理できます。

「near-leaf package」の概念

ソフトウェア設計における「リーフパッケージ(leaf package)」とは、他のどのパッケージにも依存しないパッケージを指します。一方、「ニアリーフパッケージ(near-leaf package)」は、他のパッケージへの依存が非常に少ないパッケージを指します。このようなパッケージは、依存性グラフの末端に位置するため、その変更が他の多くのパッケージに影響を与えるリスクが低く、再利用性が高く、テストが容易であるという利点があります。

このコミットでは、strconvunicodestringsのような大きなパッケージから切り離すことで、strconvを「near-leaf package」に近づけ、Go標準ライブラリ全体のモジュール性と効率性を向上させています。

技術的詳細

このコミットの技術的な核心は、strconvパッケージが外部のunicodeおよびstringsパッケージに依存することなく、必要な機能を内部で提供するように変更された点にあります。

  1. unicode.IsPrintの代替実装:

    • strconvパッケージ内に、unicode.IsPrintと同等の機能を提供する独自のIsPrint関数が実装されました。この実装は、src/pkg/strconv/isprint.goファイルに格納されています。
    • この新しいIsPrint関数は、Unicodeの表示可能文字の範囲を定義する静的なデータテーブル(isPrint16, isNotPrint16, isPrint32, isNotPrint32)を利用します。これらのテーブルは、文字コードの範囲と例外を効率的に検索できるように設計されています。
    • isPrint32isNotPrint32のデータ構造が変更され、特にisNotPrint32uint16の配列となり、各エントリに0x10000を加算することで実際のUnicodeコードポイントを表すようになりました。これにより、データサイズが最適化されています。
    • コミットメッセージにあるように、このデータテーブルの追加により、strconvパッケージのバイナリサイズが約2KB増加しますが、これはunicodeパッケージ全体をリンクすることによるサイズ増加と比較すれば非常に小さいものです。
  2. データテーブル生成ツールの変更:

    • src/pkg/strconv/makeisprint.goは、isprint.go内のデータテーブルを生成するためのツールです。このツールも、新しいデータ構造に合わせて更新されました。
    • 特に、isNotPrint32の生成ロジックが変更され、uint16として格納するために0x10000を減算する処理が追加されました。また、osパッケージがインポートされ、エラー出力にfmt.Fprintf(os.Stderr, ...)が使用されるようになりました。
    • データサイズ計算のコメントも、新しいデータ構造に合わせて修正されています。
  3. strings.Containsの代替実装:

    • src/pkg/strconv/quote.go内で使用されていたstrings.Contains関数は、strconvパッケージ内に新しく追加されたローカルなcontains関数に置き換えられました。このcontains関数は、単純なバイト列の走査によって、指定されたバイトが文字列に含まれるかを判定します。
  4. 依存関係の削除と定数の置き換え:

    • src/pkg/strconv/quote.goから"strings""unicode"のインポートが削除されました。
    • unicode.IsPrint(r)の呼び出しは、strconvパッケージ内で定義されたIsPrint(r)に置き換えられました。
    • unicode.MaxASCIIutf8.RuneSelfに、unicode.MaxRuneutf8.MaxRuneに置き換えられました。これらはunicode/utf8パッケージから提供される定数であり、UTF-8エンコーディングにおけるASCII文字の最大値や、有効なUnicodeコードポイントの最大値を表します。unicode/utf8strconvが既に依存しているパッケージであるため、新たな依存は発生しません。
  5. テストの追加:

    • src/pkg/strconv/quote_test.goTestIsPrintという新しいテスト関数が追加されました。このテストは、strconvの新しいIsPrint関数が、既存のunicode.IsPrint関数と全く同じ結果を返すことを検証します。これにより、機能的な後方互換性が保証されます。

これらの変更により、strconvパッケージは、その主要な機能である文字列変換とクォート処理を、外部の大きな依存関係なしに実行できるようになりました。これは、Goの標準ライブラリのモジュール性と効率性を高める上で重要なステップです。

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

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  1. 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": {},
    
  2. src/pkg/strconv/isprint.go: IsPrint関数の新しい実装と、そのために使用されるデータテーブル(isPrint16, isNotPrint16, isPrint32, isNotPrint32)が追加・修正されました。特にisPrint32isNotPrint32のデータが大幅に更新されています。

    --- 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)
     }
    
  3. 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")
     }
    
  4. 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 {
    
  5. 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の表示可能文字の範囲と、その例外(表示可能ではないが、範囲内に含まれる文字)を効率的に表現するために使用されます。 isPrint16isNotPrint16uint16(0x0000-0xFFFFの範囲の文字、つまりBMP: Basic Multilingual Plane)の文字を扱います。 isPrint32uint32(0x10000以上の文字、つまりサロゲートペアや追加多言語面など)の文字の範囲を扱います。 isNotPrint32は、isPrint32の範囲内で表示可能ではない文字の例外をuint16として格納します。この際、実際のUnicodeコードポイントから0x10000を引いた値が格納されます。これは、0x10000以上の文字を扱う際に、データサイズを削減するための工夫です。

  • IsPrint(r rune) bool 関数: この関数は、与えられたrune(Unicodeコードポイント)が表示可能かどうかを判定します。

    1. Latin-1 (0x00-0xFF) の高速チェック: まず、runeがLatin-1の範囲内にある場合、ASCII文字(0x20-0x7E)と一部の拡張Latin-1文字(0xA1-0xFF、ただしソフトハイフン0xADを除く)を高速に判定します。これは、これらの文字が非常に頻繁に現れるため、パフォーマンスを向上させるための最適化です。
    2. データテーブルによる検索: Latin-1の範囲外の文字については、bsearch16bsearch32といった二分探索関数を使用して、適切なデータテーブル(isPrint16またはisPrint32)を検索します。これにより、文字がどの表示可能範囲に属するかを効率的に特定します。
    3. 例外のチェック: 最後に、文字が表示可能範囲内に含まれる場合でも、isNotPrint16またはisNotPrint32テーブルにその文字が例外として登録されていないかを確認します。例外リストに存在しない場合のみ、その文字は表示可能と判定されます。
    4. 0x20000以上の文字の扱い: if r >= 0x20000 { return true }という行は、0x20000以上のUnicodeコードポイント(主に絵文字や特殊記号など)は、このIsPrintの実装ではすべて表示可能とみなすことを示しています。これは、これらの文字の表示可能性を詳細に判定するためのデータテーブルをさらに大きくすることを避けるためのトレードオフと考えられます。

このIsPrintの実装は、unicodeパッケージの完全なUnicodeプロパティテーブルを読み込むことなく、strconvが必要とする表示可能性の判定を効率的に行うことを可能にしています。

src/pkg/strconv/makeisprint.go

このファイルは、isprint.go内のデータテーブルを自動生成するためのGoプログラムです。

  • データサイズ計算の更新: コメント行のデータサイズ計算式が、新しいデータ構造(isNotPrint32uint16になったこと)に合わせて修正されています。これにより、生成されるデータが実際にどの程度のメモリを消費するかが正確に示されます。
  • isNotPrint32の生成ロジック: isNotPrint32配列に格納される値は、元のUnicodeコードポイントから0x10000を引いたuint16値として出力されるようになりました。これは、isprint.goIsPrint関数がこのオフセットを考慮して検索を行うためです。また、0x20000以上の文字がisNotPrint32に格納されようとした場合にエラーを出力し、プログラムを終了するチェックが追加されました。これは、isNotPrint32uint16で表現できる範囲(最大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.MaxASCIIutf8.RuneSelfに置き換えられました。utf8.RuneSelfは、UTF-8エンコーディングにおいて、1バイトで表現できる最大のrune(つまりASCII文字の範囲)を表す定数です。これはunicode/utf8パッケージがstrconvの既存の依存であるため、新たな依存は発生しません。
  • unicode.MaxRuneからutf8.MaxRuneへの置き換え: 有効なUnicodeコードポイントの最大値を表すunicode.MaxRuneutf8.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関数は、strconvIsPrint関数が、Goの標準unicodeパッケージのunicode.IsPrint関数と完全に同じ結果を返すことを検証します。これは、strconvが独自のIsPrint実装を持つようになったにもかかわらず、その機能が既存の期待される動作と一致していることを保証するための重要なテストです。このテストは、unicode.MaxRuneまでのすべてのUnicodeコードポイントに対してIsPrintを呼び出し、結果を比較します。最初の10個の不一致が見つかった時点でテストを終了し、エラーを報告します。

これらの変更は、strconvパッケージの自己完結性を高め、Goの標準ライブラリ全体の依存関係を簡素化するというコミットの目標を達成しています。

関連リンク

参考にした情報源リンク