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

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

このコミットは、Go言語の標準ライブラリにおける識別子の命名規則、特にエクスポート(公開)されるべき要素と内部的な要素の区別を明確にするための広範な変更を適用しています。具体的には、関数名、型名、変数名、定数名などの先頭文字の大小文字を調整し、Go言語の可視性ルール(大文字で始まる識別子はパッケージ外に公開され、小文字で始まる識別子はパッケージ内部に限定される)に準拠させています。これにより、APIの意図がより明確になり、ライブラリの利用者がどの要素を外部から利用できるかを容易に判断できるようになります。

コミット

commit aedfb397aee33a971a44c6959f4759b3bbea0022
Author: Russ Cox <rsc@golang.org>
Date:   Fri Jan 16 12:47:24 2009 -0800

    casify misc
    
    R=r
    DELTA=247  (20 added, 50 deleted, 177 changed)
    OCL=22951
    CL=22955

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

https://github.com/golang/go/commit/aedfb397aee33a971a44c6959f4759b3bbea0022

元コミット内容

casify misc

R=r
DELTA=247  (20 added, 50 deleted, 177 changed)
OCL=22951
CL=22955

変更の背景

Go言語は、その設計初期段階から、シンプルさと明確さを重視してきました。その一環として、識別子の可視性(エクスポートされるか否か)に関する明確なルールが導入されました。Goでは、識別子(変数、関数、型など)の最初の文字が大文字である場合、その識別子はパッケージ外に公開(エクスポート)され、小文字である場合はパッケージ内部に限定されます。

このコミットが行われた2009年1月は、Go言語がまだ活発に開発され、言語仕様や標準ライブラリのAPIが固まりつつある時期でした。初期のコードベースでは、この可視性ルールが完全に適用されていない箇所や、命名規則が一貫していない箇所が存在していました。

この「casify misc」コミットの背景には、以下の目的があったと考えられます。

  1. APIの明確化: どの関数や型が外部から利用可能で、どれが内部実装の詳細であるかを明確にすることで、ライブラリの利用者が混乱することなく、意図されたAPIのみを使用できるようにする。
  2. コードの一貫性: Go言語の命名規則と可視性ルールにコードベース全体を準拠させ、一貫性のあるコーディングスタイルを確立する。
  3. 将来の変更への対応: 内部実装の詳細を隠蔽することで、将来的に内部構造を変更しても、外部APIに影響を与えずに済むようにする。
  4. ドキュメンテーションの簡素化: エクスポートされたAPIのみがドキュメントに表示されるため、ドキュメントがより簡潔で分かりやすくなる。

特に、stringsパッケージやrandパッケージなど、基本的な機能を提供するライブラリにおいて、この可視性の明確化は非常に重要でした。これにより、Go言語の設計思想である「シンプルさ」と「実用性」がより一層強化されました。

前提知識の解説

このコミットの変更内容を理解するためには、Go言語における以下の基本的な概念を理解しておく必要があります。

  1. パッケージ (Packages): Go言語のコードは「パッケージ」という単位で整理されます。パッケージは関連する機能の集合であり、コードのモジュール化と再利用を促進します。各Goファイルは必ずpackage宣言を持ち、同じディレクトリ内のファイルは通常、同じパッケージに属します。

  2. 識別子の可視性 (Visibility of Identifiers): Go言語には、C++やJavaのようなpublic, private, protectedといった明示的なアクセス修飾子が存在しません。代わりに、識別子の最初の文字の大小文字によって可視性が決定されます。

    • エクスポートされた識別子 (Exported Identifiers): 識別子(変数、関数、型、メソッド、構造体のフィールドなど)の最初の文字が大文字で始まる場合、その識別子はパッケージ外からアクセス可能です。これは、他の言語のpublicに相当します。
    • エクスポートされない識別子 (Unexported Identifiers): 識別子の最初の文字が小文字で始まる場合、その識別子は宣言されたパッケージ内でのみアクセス可能です。これは、他の言語のprivateに相当します。
  3. 標準ライブラリ (Standard Library): Go言語は、豊富な標準ライブラリを提供しており、HTTP通信、I/O操作、文字列処理、乱数生成、JSON処理など、多岐にわたる機能が含まれています。これらのライブラリは、Goプログラム開発の基盤となります。このコミットで変更されているファイルは、まさにこれらの標準ライブラリの一部です。

  4. UTF-8エンコーディング (UTF-8 Encoding): Go言語は文字列をUTF-8で扱います。unicode/utf8パッケージは、UTF-8エンコードされたバイト列からUnicodeコードポイント(rune)をデコードしたり、その逆を行ったりするための機能を提供します。このコミットでは、utf8パッケージ内の内部ヘルパー関数の可視性が調整されています。

これらの前提知識を踏まえることで、なぜ特定の識別子の大小文字が変更されたのか、その変更がGo言語の設計思想とどのように合致するのかを深く理解することができます。

技術的詳細

このコミットの技術的詳細は、Go言語の可視性ルールをコードベース全体に徹底的に適用した点にあります。変更は主に以下のパターンに分類できます。

  1. 公開APIの明確化(小文字から大文字へ):

    • stringsパッケージのsplit, index, count, explode, joinといった関数が、それぞれSplit, Index, Count, Explode, Joinへと変更されています。これらは文字列操作の基本的な機能であり、パッケージ外から利用されることが想定されるため、大文字で始まる名前に変更され、エクスポートされるようになりました。
    • randパッケージの乱数生成関数群(例: rand63からInt63urand32からUint32nrandからIntnfrandからFloatpermからPermなど)も同様に、外部から利用されるべきAPIとして大文字で始まる名前に変更されています。
    • mallocパッケージのStats型がexport type Statsと明示的にエクスポートされるようになりました。
  2. 内部ヘルパーの隠蔽(大文字から小文字へ、またはアンダースコアプレフィックス):

    • ioパッケージのFullRead型が_FullReadに、関連するMakeFullReader関数がMake_FullReaderに変更されています。これは、これらの型や関数がパッケージ内部でのみ使用されることを意図しており、外部に公開する必要がないため、アンダースコアプレフィックスを付けて内部的な要素であることを強調しています。
    • onceパッケージのJob型が_Jobに、Request型が_Requestに、そしてServer関数がserverにそれぞれ変更されています。onceパッケージは、特定の処理が一度だけ実行されることを保証するためのメカニズムを提供しますが、その内部実装に使われる型や関数は外部から直接操作されるべきではありません。したがって、これらは内部的な要素として小文字(またはアンダースコアプレフィックス)に変更され、隠蔽されました。
    • utf8パッケージのDecodeRuneInternalDecodeRuneInStringInternalといった内部ヘルパー関数も小文字に変更され、パッケージ内部でのみ利用されるようになりました。
    • randパッケージ内の定数(LEN, TAP, MASKなど)も_LEN, _TAP, _MASKのようにアンダースコアプレフィックスが付けられ、内部的な定数であることを示しています。
  3. テストコードの調整: 公開APIの変更に伴い、テストコード内の関数呼び出しも新しい大文字の関数名に更新されています。また、テストヘルパー関数も内部的なものとして小文字に変更されています(例: utf8_test.goBytesbytesに、EqualBytesequalBytesに)。

これらの変更は、Go言語の設計原則である「明示的なAPIと隠蔽された実装」を徹底するための重要なステップでした。これにより、Goの標準ライブラリはより堅牢で、理解しやすく、保守しやすいものとなりました。特に、stringsrandのような頻繁に利用されるパッケージにおいて、どの関数が公開APIであるかが明確になったことは、開発者にとって大きなメリットとなります。

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

このコミットでは、複数のファイルにわたって広範な変更が行われていますが、特にGo言語の可視性ルールを象徴する変更として、以下のファイルとコードスニペットが挙げられます。

  1. src/lib/http/request.go および src/lib/http/url.go: stringsパッケージの関数呼び出しが小文字から大文字に変更されています。

    --- a/src/lib/http/request.go
    +++ b/src/lib/http/request.go
    @@ -190,7 +190,7 @@ export func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) {
     	}
     
     	var f []string;
    -	if f = strings.split(s, " "); len(f) != 3 {
    +	if f = strings.Split(s, " "); len(f) != 3 {
     		return nil, BadRequest
     	}
     	req.method, req.rawurl, req.proto = f[0], f[1], f[2];
    
    --- a/src/lib/http/url.go
    +++ b/src/lib/http/url.go
    @@ -156,7 +156,7 @@ export func ParseURL(rawurl string) (url *URL, err *os.Error) {
     	}
     
     	// If there's no @, split's default is wrong.  Check explicitly.
    -	if strings.index(url.authority, "@") < 0 {
    +	if strings.Index(url.authority, "@") < 0 {
     		url.host = url.authority;
     	} else {
     		url.userinfo, url.host = split(url.authority, '@', true);
    

    これは、stringsパッケージのsplitindexが、外部から利用可能なSplitIndexとしてエクスポートされるようになったことを示しています。

  2. src/lib/once.go: onceパッケージの内部的な型と関数がアンダースコアプレフィックスまたは小文字に変更され、外部からのアクセスが制限されています。

    --- a/src/lib/once.go
    +++ b/src/lib/once.go
    @@ -11,30 +11,29 @@
     
     package once
     
    -type Job struct {
    +type _Job struct {
     	done bool;
     	doit chan bool;	// buffer of 1
     }
     
    -type Request struct {
    +type _Request struct {
     	f *();
    -	reply chan *Job
    +	reply chan *_Job
     }
     
    -// TODO: Would like to use chan Request but 6g rejects it.
    -var service = make(chan *Request)
    -var jobmap = make(map[*()]*Job)
    +var service = make(chan _Request)
    +var jobmap = make(map[*()]*_Job)
     
     // Moderate access to the jobmap.
     // Even if accesses were thread-safe (they should be but are not)
     // something needs to serialize creation of new jobs.
     // That's what the Server does.
    -func Server() {
    +func server() {
     	for {
     		req := <-service;
     		job, present := jobmap[req.f];
     		if !present {
    -			job = new(Job);
    +			job = new(_Job);
     			job.doit = make(chan bool, 1);
     			job.doit <- true;
     			jobmap[req.f] = job
    @@ -48,13 +47,12 @@ export func Do(f *()) {
     	// If not there, ask map server to make one.
     	// TODO: Uncomment use of jobmap[f] once
     	// maps are thread-safe.
    -	var job *Job;
    +	var job *_Job;
     	var present bool;
     	// job, present = jobmap[f]
     	if !present {
    -		c := make(chan *Job);
    -		req := Request{f, c};
    -		service <- &req;
    +		c := make(chan *_Job);
    +		service <- _Request{f, c};
     		job = <-c
     	}
     
    @@ -74,6 +72,6 @@ export func Do(f *()) {
     }
     
     func init() {
    -	go Server()
    +	go server()
     }
    

    JobRequest_Job_Requestに、Serverserverに変更され、これらがパッケージ内部でのみ利用されることを示しています。

  3. src/lib/rand.go: 乱数生成関数の名前が、Goの命名規則に従って大文字で始まる名前に変更されています。

    --- a/src/lib/rand.go
    +++ b/src/lib/rand.go
    @@ -10,60 +10,54 @@
     
     package	rand
     
    -// rand, rand31, rand63 - return non-negative random int, int32, int64
    +// rand, rand31, Int63 - return non-negative random int, int32, int64
     // urand32 - return random uint32
    -// nrand, nrand31, nrand63 - return 0 <= random < n
    +// nrand, nrand31, Int63n - return 0 <= random < n
     // frand, frand64, frand32 - return 0 <= random float, float64, float32 < 1
     // perm gives a random permutation []int
     
    -const
    -(
    -	LEN	 = 607;
    -	TAP	 = 273;
    -	MASK	 = (1<<63)-1;
    -	A	 = 48271;
    -	M	 = 2147483647;
    -	Q	 = 44488;
    -	R	 = 3399;
    +const (
    +	_LEN	 = 607;
    +	_TAP	 = 273;
    +	_MASK	 = (1<<63)-1;
    +	_A	 = 48271;
    +	_M	 = 2147483647;
    +	_Q	 = 44488;
    +	_R	 = 3399;
     )
     
    -var
    -(
    -	rng_cooked	[LEN]int64;	// cooked random numbers
    -	rng_vec		[LEN]int64;	// current feedback register
    +var (
    +	rng_cooked	[_LEN]int64;	// cooked random numbers
    +	rng_vec		[_LEN]int64;	// current feedback register
     	rng_tap		int;		// index into vector
     	rng_feed	int;		// index into vector
     )
     
    -func
    -seedrand(x int32) int32
    -{
    +func seedrand(x int32) int32 {
     	// seed rng x[n+1] = 48271 * x[n] mod (2**31 - 1)
    -	hi := x / Q;
    -	lo := x % Q;
    -	x = A*lo - R*hi;
    +	hi := x / _Q;
    +	lo := x % _Q;
    +	x = _A*lo - _R*hi;
     	if x < 0 {
    -		x += M;
    +		x += _M;
     	}
     	return x;
     }
     
    -export func
    -srand(seed int32)
    -{
    +export func Seed(seed int32) {
     	rng_tap = 0;
    -	rng_feed = LEN-TAP;
    +	rng_feed = _LEN-_TAP;
     
    -	seed = seed%M;
    +	seed = seed%_M;
     	if seed < 0 {
    -		seed += M;
    +		seed += _M;
     	}
     	if seed == 0 {
     		seed = 89482311;
     	}
     
     	x := seed;
    -	for i := -20; i < LEN; i++ {
    +	for i := -20; i < _LEN; i++ {
     		x = seedrand(x);
     		if i >= 0 {
     			var u int64;
    @@ -73,105 +67,84 @@ srand(seed int32)
     			x = seedrand(x);
     			u ^= int64(x);
     			u ^= rng_cooked[i];
    -			rng_vec[i] = u & MASK;
    +			rng_vec[i] = u & _MASK;
     		}
     	}
     }
     
    -export func
    -rand63() int64
    -{
    +export func Int63() int64 {
     	rng_tap--;
     	if rng_tap < 0 {
    -		rng_tap += LEN;
    +		rng_tap += _LEN;
     	}
     
     	rng_feed--;
     	if rng_feed < 0 {
    -		rng_feed += LEN;
    +		rng_feed += _LEN;
     	}
     
    -	x := (rng_vec[rng_feed] + rng_vec[rng_tap]) & MASK;
    +	x := (rng_vec[rng_feed] + rng_vec[rng_tap]) & _MASK;
     	rng_vec[rng_feed] = x;
     	return x;
     }
     
    -export func
    -urand32() uint32
    -{
    -	return uint32(rand63() >> 31);
    +export func Uint32() uint32 {
    +	return uint32(Int63() >> 31);
     }
     
    -export func
    -rand31() int32
    -{
    -	return int32(rand63() >> 32);
    +export func Int31() int32 {
    +	return int32(Int63() >> 32);
     }
     
    -export func
    -rand() int
    -{
    -	u := uint(rand63());
    +export func Int() int {
    +	u := uint(Int63());
     	return int(u << 1 >> 1);	// clear sign bit if int == int32
     }
     
    -export func
    -nrand63(n int64) int64
    -{
    +export func Int63n(n int64) int64 {
     	if n <= 0 {
     		return 0
     	}
     	max := int64((1<<63)-1 - (1<<63) % uint64(n));
    -	v := rand63();
    +	v := Int63();
     	for v > max {
    -		v = rand63()
    +		v = Int63()
     	}
     	return v % n
     }
     
    -export func
    -nrand31(n int32) int32
    -{
    -	return int32(nrand63(int64(n)))
    +export func Int31n(n int32) int32 {
    +	return int32(Int63n(int64(n)))
     }
     
    -export func
    -nrand(n int) int
    -{
    -	return int(nrand63(int64(n)))
    +export func Intn(n int) int {
    +	return int(Int63n(int64(n)))
     }
     
    -export func
    -frand64() float64
    -{
    -	x := float64(rand63()) / float64(MASK);
    +export func Float64() float64 {
    +	x := float64(Int63()) / float64(_MASK);
     	for x >= 1 {
    -		x = float64(rand63()) / float64(MASK);
    +		x = float64(Int63()) / float64(_MASK);
     	}
     	return x;
     }
     
    -export func
    -frand32() float32
    -{
    -	return float32(frand64())
    +export func Float32() float32 {
    +	return float32(Float64())
     }
     
    -export func
    -frand() float
    +export func Float() float
     {
    -	return float(frand64())
    +	return float(Float64())
     }
     
    -export func
    -perm(n int) []int
    -{
    +export func Perm(n int) []int {
     	m := make([]int, n);
     	for i:=0; i<n; i++ {
     		m[i] = i;
     	}
     	for i:=0; i<n; i++ {
    -		j := nrand(n);
    +		j := Intn(n);
     		t := m[i];
     		m[i] = m[j];
     		m[j] = t;
    @@ -179,9 +152,7 @@ perm(n int) []int
     	return m;
     }
     
    -func
    -init()
    -{
    +func init() {
     	// the state of the rng
     	// after 780e10 iterations
     
    @@ -793,5 +764,5 @@ init()
     	rng_cooked[605] = 9103922860780351547;
     	rng_cooked[606] = 4152330101494654406;
     
    -	srand(1);
    +	Seed(1);
     }
    

    この変更は、randパッケージのAPIがよりGoらしい命名規則に準拠し、外部から利用される関数が明確になったことを示しています。

コアとなるコードの解説

上記の変更箇所は、Go言語の設計哲学と可視性ルールを直接反映しています。

  • stringsパッケージの関数名変更 (split -> Splitなど): stringsパッケージは、Goプログラムで最も頻繁に利用されるパッケージの一つです。splitindexのような関数は、文字列操作の基本的なプリミティブであり、Goの他の言語機能(例: for ... rangeループでの文字列イテレーション)と組み合わせて利用されます。これらの関数が小文字で始まっていた場合、Goの可視性ルールに従えばパッケージ内部でのみ利用可能ということになりますが、実際には外部から利用されることが意図されていました。このコミットにより、これらの関数名がSplitIndexのように大文字で始まるように変更されたことで、Goの可視性ルールに完全に準拠し、これらの関数がstringsパッケージの公開APIの一部であることが明確になりました。これにより、開発者は自信を持ってこれらの関数をインポートして利用できるようになります。

  • onceパッケージの内部要素の隠蔽 (Job -> _Job, Server -> serverなど): sync.Once(このコミット時点ではonceパッケージ)は、並行処理において特定の初期化処理が一度だけ実行されることを保証するための重要なメカニズムです。このパッケージの内部には、その機能を実現するための補助的な型(Job, Request)や関数(Server)が存在します。これらはsync.Onceの公開API(Doメソッド)を実装するための内部的な詳細であり、外部のコードから直接アクセスされるべきではありません。もしこれらがエクスポートされたままだと、開発者が誤って内部実装に依存するコードを書いてしまい、将来のライブラリの変更によってそのコードが壊れるリスクがありました。このコミットでは、これらの内部要素の名前を小文字(またはアンダースコアプレフィックス)に変更することで、Goの可視性ルールに従ってこれらをパッケージ内部に限定し、APIの安定性とライブラリの保守性を向上させています。

  • randパッケージの関数名変更と定数のプレフィックス追加: randパッケージは乱数生成機能を提供します。このコミット以前は、randrand63nrandなどの関数名が小文字で始まっていました。これらはGoの可視性ルールからすると内部関数に見えますが、実際には外部から利用されるべきAPIでした。このコミットにより、これらの関数はIntInt63Intnのように、より一般的でGoらしい命名規則に従い、かつ大文字で始まる名前に変更されました。これにより、これらの関数がrandパッケージの公開APIであることが明確になりました。 また、LENTAPMASKなどの内部的な定数に_プレフィックスが追加されたのは、これらがパッケージ内部でのみ使用される実装の詳細であり、外部に公開する必要がないことを示しています。これは、Goの慣習として、内部的な定数や変数にアンダースコアプレフィックスを付けることで、そのスコープを明確にする手法が用いられることがあります。

これらの変更は、Go言語の「シンプルさ」と「明示性」という設計原則を具現化したものです。APIの意図を明確にし、内部実装の詳細を隠蔽することで、Goの標準ライブラリはより使いやすく、堅牢なものへと進化しました。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(GitHubリポジトリ): https://github.com/golang/go
  • Go言語のコミット履歴: https://github.com/golang/go/commits/master
  • Go言語の初期の設計に関する議論やメーリングリストのアーカイブ(一般公開されているものがあれば)
  • Go言語の命名規則に関する一般的なガイドラインやベストプラクティスに関する記事。