[インデックス 1614] ファイルの概要
このコミットは、Go言語の初期のチュートリアルドキュメントである doc/go_tutorial.txt
に対する修正です。主に、Go言語の基本的な概念(パッケージ、new
とmake
、参照型、エクスポートルール、インターフェース、チャネルの動作など)に関する説明をより明確にし、読者の理解を深めることを目的としています。Rob Pike氏によって行われたこの変更は、初期のGo言語の学習体験を向上させるための重要な改善点を含んでいます。
コミット
commit 6aabf31a83f692b1f8962154eab66e7e22cb5cab
Author: Rob Pike <r@golang.org>
Date: Wed Feb 4 15:13:07 2009 -0800
a few tweaks triggered by tgs's comments
DELTA=46 (25 added, 1 deleted, 20 changed)
OCL=24342
CL=24354
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6aabf31a83f692b1f8962154eab66e7e22cb5cab
元コミット内容
a few tweaks triggered by tgs's comments
変更の背景
このコミットは、Go言語の初期段階において、チュートリアルドキュメント doc/go_tutorial.txt
の内容を改善するために行われました。コミットメッセージにある「tgs's comments」という記述から、おそらく初期のレビューアやユーザーからのフィードバックに基づいて、既存の説明が不明瞭であったり、誤解を招く可能性のある箇所が特定され、それらを修正・明確化する必要があったと考えられます。
特に、Go言語のユニークな特徴であるnew
とmake
の使い分け、参照型の挙動、エクスポートのルール、インターフェースの概念、そして並行処理におけるチャネルのブロッキング動作などは、他のプログラミング言語の経験者にとっては直感的に理解しにくい点が多く、初期の学習者がつまずきやすいポイントでした。このコミットは、これらの核心的な概念に対する説明をより正確かつ詳細にすることで、Go言語の導入障壁を低減し、よりスムーズな学習体験を提供することを目的としています。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の基本的な概念を把握しておく必要があります。
-
Goのパッケージとエクスポートルール:
- Go言語では、コードは「パッケージ」という単位で整理されます。
- パッケージ内の識別子(変数、関数、型など)は、その名前の最初の文字が大文字であるか小文字であるかによって、外部に公開されるか(エクスポートされるか)否かが決まります。大文字で始まる識別子はエクスポートされ、パッケージ外からアクセス可能です。小文字で始まる識別子はパッケージ内でのみアクセス可能です。これはGoの設計思想の一つであり、明示的なアクセス修飾子(
public
,private
など)を持たない代わりに、命名規則によって可視性を制御します。
-
new
とmake
の違い:- Go言語には、メモリを割り当てるための2つの組み込み関数
new
とmake
があります。 new(T)
: 型T
のゼロ値(または初期値)を持つ新しい項目を割り当て、その項目へのポインタ(*T
型)を返します。これは、C++のnew
やJavaのnew
とは異なり、メモリを割り当てるだけで初期化は行いません(ゼロ値が設定される)。make(T, args)
: スライス、マップ、チャネルといった組み込みの参照型(reference types)のみを初期化するために使用されます。make
は、これらの型に必要な内部データ構造を割り当て、使用可能な状態に初期化されたT
型の値を返します。new
とは異なり、ポインタではなく、初期化された型そのものを返します。
- Go言語には、メモリを割り当てるための2つの組み込み関数
-
参照型(Reference Types):
- Go言語におけるスライス(
[]T
)、マップ(map[K]V
)、チャネル(chan T
)は参照型です。これらの型は、内部的にデータへのポインタを持っており、変数をコピーしても同じ基盤データを参照します。そのため、一方の変数を介して内容を変更すると、もう一方の変数からもその変更が見えます。 - これらの型は、
make
関数を使って適切に初期化しないと、nil
(ゼロ値)の状態では使用できません。nil
のスライス、マップ、チャネルは、それぞれ長さ0、要素なし、通信不可といった状態であり、操作しようとするとランタイムエラーを引き起こす可能性があります。
- Go言語におけるスライス(
-
インターフェース(Interfaces):
- Goのインターフェースは、メソッドのシグネチャの集合を定義する型です。特定のインターフェースのすべてのメソッドを実装する任意の型は、そのインターフェースを満たしていると見なされます(暗黙的な実装)。
- インターフェースは、ポリモーフィズムを実現するための強力なメカニズムであり、具体的な実装から抽象化された振る舞いを定義できます。
-
型アサーション(Type Assertion):
- インターフェース型の変数に格納されている基底の具体的な値にアクセスしたり、その値が特定のインターフェースを満たしているかを確認したりするために使用されます。
- 構文は
value.(Type)
で、value
がType
に変換可能であれば、その型の値とtrue
を返します。変換不可能であれば、ゼロ値とfalse
を返します(2値返却形式の場合)。
-
複合リテラル(Composite Literals):
- 構造体、配列、スライス、マップなどの複合型を簡潔に初期化するための構文です。例えば、
MyStruct{field1: value1, field2: value2}
のように記述できます。
- 構造体、配列、スライス、マップなどの複合型を簡潔に初期化するための構文です。例えば、
-
Goの並行処理(Goroutines and Channels):
- Goroutine: Goランタイムによって管理される軽量なスレッドです。
go
キーワードを使って関数呼び出しの前に記述することで、その関数を新しいgoroutineで実行できます。 - Channel: goroutine間で値を安全に送受信するための通信メカニズムです。チャネルへの送信操作(
ch <- value
)と受信操作(value <- ch
)は、デフォルトでブロッキング動作をします。つまり、送信側は受信側が準備できるまで待ち、受信側は送信側が準備できるまで待ちます。これにより、同期が自動的に行われます。
- Goroutine: Goランタイムによって管理される軽量なスレッドです。
技術的詳細
このコミットにおける技術的な変更は、Go言語の基本的な概念に対する理解を深めるための重要な修正を含んでいます。
-
new()
とmake()
の明確化:- 初期のGo言語学習者にとって、
new()
とmake()
の使い分けは混乱の元でした。特に、マップ、スライス、チャネルといった参照型をnew()
で割り当てると、nil
ポインタが返され、そのままでは使用できないという点が強調されています。 new(T)
が*T
を返し、make(T)
がT
を返すという違いが明記され、new()
で参照型を割り当てた場合の「未初期化の参照へのポインタ」という結果が、宣言された未初期化変数のアドレスを取るのと同等であると説明されています。これにより、参照型を適切に初期化するにはmake()
を使用する必要があるという点が明確になりました。
- 初期のGo言語学習者にとって、
-
パッケージのエクスポートルールに関する補足:
- 識別子の可視性(エクスポートされるか否か)が、単なる慣習ではなく「コンパイラによって強制されるルール」であることが追記されました。これにより、Goの命名規則が単なるスタイルガイドではなく、言語仕様の一部であることが強調され、より厳密な理解が促されます。
-
複合リテラルとヒープ割り当て:
- 複合リテラルが「新しいヒープ割り当てオブジェクトを構築する」ために使用できるという説明が追加されました。これにより、複合リテラルが単に値を初期化するだけでなく、メモリ割り当ての側面も持つことが示唆され、より深い理解に繋がります。
-
String
メソッドとtype assertion
の詳細:String()
メソッドが「印刷規則」のために特別に扱われること(fmt
パッケージなどによる)が示唆されました。これは、Goのfmt.Stringer
インターフェースの基礎となる概念であり、オブジェクトの文字列表現をカスタマイズするための重要なパターンです。v.(String)
という構文が「型アサーション」であると明示的に記述されました。これにより、インターフェース変数の基底の型を検査し、必要に応じて変換するGoのメカニズムが明確に示されます。String
という名前が型名とメソッド名の両方で使われる場合の曖昧さがないことについて、詳細な説明が追加されました。これは、Goのスコープ規則とメソッド解決の仕組みを理解する上で重要です。メソッド名は常に変数と関連付けて呼び出されるため、型名と衝突しないという点が強調されています。同様の例としてio.Write
インターフェースも挙げられています。
-
チャネルのブロッキング動作の明確化:
- チャネルの操作(送信および受信)が「ブロッキング動作」をすることがより明確に記述されました。これはGoの並行処理モデルの核心であり、goroutine間の同期と通信をシンプルかつ安全に行うための基盤です。送信側は受信側が準備できるまで待ち、受信側は送信側が準備できるまで待つという挙動が、デッドロックや競合状態を避ける上でいかに重要であるかが示唆されます。
-
関数リテラル(匿名関数)の導入:
sieve1.go
の例で、関数リテラル(匿名関数)の構文が導入され、その場で関数を構築して呼び出すことができるという説明が追加されました。これは、Goでクロージャやコールバック、そしてgoroutineを簡潔に記述するための強力な機能です。
これらの変更は、Go言語の設計思想、特にシンプルさ、明示性、そして安全性に焦点を当てたものです。初期のチュートリアルでこれらの概念を正確に伝えることは、言語の普及と正しい利用を促進する上で不可欠でした。
コアとなるコードの変更箇所
doc/go_tutorial.txt
ファイルにおける主要な変更箇所は以下の通りです。
-
日付の更新:
--- doc/go_tutorial.txt +++ doc/go_tutorial.txt @@ -4,7 +4,7 @@ Let's Go Rob Pike ---- -(January 20, 2009) +(February 4, 2009)
-
package
ステートメントの説明の明確化:@@ -34,7 +34,7 @@ Let's start in the usual way: --PROG progs/helloworld.go -Every Go source file declares which package it's part of using a "package" statement. +Every Go source file declares, using a "package" statement, which package it's part of. The "main" package's "main" function is where the program starts running (after any initialization).
-
make()
とnew()
の詳細な説明:@@ -256,22 +256,24 @@ or the more idiomatic Some types - maps, slices, and channels (see below) have reference semantics. If you're holding a slice or a map and you modify its contents, other variables -referencing the same underlying data will see the modification. If you allocate -a reference object with "new()" you receive a pointer to an uninitialized ("nil") -reference. Instead, for these three types you want to use "make()": +referencing the same underlying data will see the modification. For these three +types you want to use the built-in function "make()": m := make(map[string] int); -This statement initializes a new map ready to store entries. If you just declare -the map, as in +This statement initializes a new map ready to store entries. +If you just declare the map, as in var m map[string] int; -it is a "nil" reference that cannot hold anything. To use the map, +it creates a "nil" reference that cannot hold anything. To use the map, you must first initialize the reference using "make()" or by assignment to an existing map. -Note that "new(T)" returns type "*T" while "make(T)" returns type "T". +Note that "new(T)" returns type "*T" while "make(T)" returns type +"T". If you (mistakenly) allocate a reference object with "new()", +you receive a pointer to an uninitialized reference, equivalent to +declaring an uninitialized variable and taking its address.
-
エクスポートルールの補足:
@@ -328,7 +328,8 @@ that is, by users of the package. In Go the rule about visibility of informati simple: if a name (of a top-level type, function, method, constant, variable, or of a structure field) is capitalized, users of the package may see it. Otherwise, the name and hence the thing being named is visible only inside the package in which -it is declared. In Go, the term for publicly visible names is ''exported''. +it is declared. This is more than a convention; the rule is enforced by the compiler. +In Go, the term for publicly visible names is ''exported''.
-
複合リテラルの説明の修正:
@@ -339,7 +339,8 @@ First, though, here is a factory to create them: This returns a pointer to a new "FD" structure with the file descriptor and name filled in. This code uses Go's notion of a ''composite literal'', analogous to -the ones used to build maps and arrays, to construct the object. We could write +the ones used to build maps and arrays, to construct a new heap-allocated +object. We could write
-
String
メソッドとエラーメッセージの変更:@@ -390,9 +394,12 @@ There is no implicit "this" and the receiver variable must be used to access members of the structure. Methods are not declared within the "struct" declaration itself. The "struct" declaration defines only data members. In fact, methods can be created for any type you name, such as an integer or -array, not just for "structs". We'll see an an example with arrays later. +array, not just for "structs". We'll see an example with arrays later. -These methods use the public variable "os.EINVAL" to return the ("*os.Error" +The "String" method is so called because of printing convention we'll +describe later. +\n+The methods use the public variable "os.EINVAL" to return the ("*os.Error" version of the) Unix error code EINVAL. The "os" library defines a standard set of such error values. @@ -404,7 +411,7 @@ and run the program: % helloworld3 hello, world - can't open file; errno=2 + can't open file; err=No such file or directory %
-
インターフェースとポリモーフィズムの説明、
type assertion
の明示:@@ -504,8 +511,12 @@ useful for things like containers. Sorting ---- -As another example of interfaces, consider this simple sort algorithm, -taken from "progs/sort.go": +Interfaces provide a simple form of polymorphism since they completely +separate the definition of what an object does from how it does it, allowing +distinct implementations to be represented at different times by the +same interface variable.\n+\n+As an example, consider this simple sort algorithm taken from "progs/sort.go": --PROG progs/sort.go /func.Sort/ /^}/\ @@ -628,7 +639,7 @@ Schematically, given a value "v", it does this: result = default_output(v) }\ -The code tests if the value stored in +The code uses a ``type assertion'' ("v.(String)") to test if the value stored in "v" satisfies the "String" interface; if it does, "s" will become an interface variable implementing the method and "ok" will be "true". We then use the interface variable to call the method. @@ -637,6 +648,14 @@ operations such as type conversion, map update, communications, and so on, although this is the only appearance in this tutorial.) If the value does not satisfy the interface, "ok" will be false.\n+\n+In this snippet "String" is used as both a type name and a method name. This does\n+not create any ambiguity because methods only appear in association\n+with a variable ("s.String()"); a method name can never appear in a context\n+where a type name is legal and vice versa. Another way to say this is that the\n+method "String" is only available within the scope bound to a variable of type\n+"String". We double-use the name because it makes the interface type\n+self-describing ("String" (the interface) implements "String" (the method)).\n+\n One last wrinkle. To complete the suite, besides "Printf" etc. and "Sprintf"\n etc., there are also "Fprintf" etc. Unlike in C, "Fprintf"'s first argument is\n not a file. Instead, it is a variable of type "io.Write", which is an\n @@ -646,6 +665,8 @@ interface type defined in the "io" library:\n Write(p []byte) (n int, err *os.Error);\n }\n \n +(This interface is another doubled name, this time for "Write"; there are also\n +"io.Read", "io.ReadWrite", and so on.)\n Thus you can call "Fprintf" on any type that implements a standard "Write()"\n method, not just files but also network channels, buffers, rot13ers, whatever\n you want.\n ```
-
チャネルのブロッキング動作の明確化と関数リテラルの説明:
@@ -686,7 +707,7 @@ Here is the first function in "progs/sieve.go": The "generate" function sends the sequence 2, 3, 4, 5, ... to its argument channel, "ch", using the binary communications operator "<-". -Channels block, so if there's no recipient for the the value on "ch", +Channel operations block, so if there's no recipient for the value on "ch", the send operation will wait until one becomes available.\n The "filter" function has three arguments: an input channel, an output @@ -734,8 +755,11 @@ This version does all the setup internally. It creates the output channel, launches a goroutine internally using a function literal, and returns the channel to the caller. It is a factory for concurrent execution, starting the goroutine and returning its connection. -The same -change can be made to "filter": +\n+The function literal notation (lines 6-10) allows us to construct an\n+anonymous function and invoke it on the spot.\n+\n+The same change can be made to "filter": --PROG progs/sieve1.go /func.filter/ /^}/\
コアとなるコードの解説
このコミットは、doc/go_tutorial.txt
というGo言語のチュートリアルドキュメントに対して、主に以下の重要な概念の明確化と詳細化を行っています。
-
package
ステートメントの表現の改善:- 変更前:
Every Go source file declares which package it's part of using a "package" statement.
- 変更後:
Every Go source file declares, using a "package" statement, which package it's part of.
- 解説: 意味合いは大きく変わりませんが、より自然な英語表現に修正されています。Goのソースファイルが
package
ステートメントを使ってどのパッケージに属するかを宣言するという、基本的なファイル構造のルールを再確認しています。
- 変更前:
-
new()
とmake()
の厳密な区別:- 変更点: マップ、スライス、チャネルといった参照型を
new()
で割り当てると「未初期化のnil
参照へのポインタ」が返され、そのままでは使用できないことが強調されました。これらの型はmake()
を使って初期化する必要があることが明確にされています。また、new(T)
が*T
を返し、make(T)
がT
を返すという型システム上の違いも明記されました。 - 解説: これはGo言語の初心者にとって最も混乱しやすい点の一つです。C++やJavaの
new
演算子とは異なり、Goのnew
はメモリを割り当てるだけで、参照型を「使用可能な状態」にはしません。make
は、参照型が持つ内部データ構造(例えば、スライスの基底配列、マップのハッシュテーブル)を適切に初期化し、すぐに使える状態にします。この修正により、参照型の正しい初期化方法が明確になり、nil
参照によるランタイムエラーを避けるための重要な指針が示されました。
- 変更点: マップ、スライス、チャネルといった参照型を
-
エクスポートルールの「強制力」の強調:
- 変更点: 識別子の可視性(大文字で始まる名前がエクスポートされること)が、単なる「慣習」ではなく「コンパイラによって強制されるルール」であることが追記されました。
- 解説: Go言語の設計哲学の一つに「明示性」があります。この変更は、命名規則が単なるコーディングスタイルではなく、言語のセマンティクスに深く組み込まれていることを明確にしています。これにより、開発者はGoの可視性ルールをより厳密に理解し、意図しないアクセスを防ぐことができます。
-
複合リテラルによる「ヒープ割り当て」の言及:
- 変更点: 複合リテラルが「新しいヒープ割り当てオブジェクトを構築する」ために使用できるという説明が追加されました。
- 解説: 複合リテラルは、構造体や配列などを簡潔に初期化する便利な構文ですが、この変更により、それが単に値を設定するだけでなく、必要に応じてメモリ(特にヒープメモリ)を割り当てる操作も伴うことが示唆されました。これにより、メモリ管理の観点からも複合リテラルの挙動をより正確に理解できるようになります。
-
String
メソッドと型アサーションの詳細な説明:- 変更点:
String()
メソッドが「印刷規則」のために特別に扱われること(fmt
パッケージなどによる)が示唆されました。また、v.(String)
という構文が「型アサーション」であると明示的に記述され、インターフェース変数の基底の型を検査するメカニズムであることが説明されました。さらに、String
という名前が型名とメソッド名の両方で使われる場合の曖昧さがないことについて、Goのスコープ規則とメソッド解決の仕組みに基づいて詳細な解説が追加されました。io.Write
インターフェースも同様の例として挙げられています。 - 解説: Goのインターフェースは強力なポリモーフィズムを提供しますが、その動作、特に型アサーションや、
Stringer
インターフェースのような特定のメソッド名が持つ慣習的な意味は、学習者にとって理解が難しい場合があります。この修正は、これらの概念をより深く掘り下げ、Goのインターフェースがどのように機能し、どのように設計されているかを明確にしています。特に、型名とメソッド名が同じでも衝突しないという説明は、Goの命名とスコープの柔軟性を示しています。
- 変更点:
-
チャネルの「ブロッキング」動作の強調:
- 変更点: チャネルの操作(送信および受信)が「ブロッキング動作」をすることがより明確に記述されました。
- 解説: Goの並行処理モデルの核心は、goroutineとチャネルによる「通信による共有」です。チャネルのブロッキング動作は、goroutine間の同期を自動的に行い、競合状態を避けるための重要な特性です。この明確化により、開発者はチャネルを介した通信がどのように同期を保証し、デッドロックを避けるためにどのように設計すべきかをより正確に理解できます。
-
関数リテラル(匿名関数)の導入:
- 変更点:
sieve1.go
の例で、関数リテラル(匿名関数)の構文が導入され、その場で関数を構築して呼び出すことができるという説明が追加されました。 - 解説: 関数リテラルは、Goでクロージャやコールバック、そしてgoroutineを簡潔に記述するための非常に強力な機能です。この導入により、チュートリアルはGoの表現力をより完全に示すことができるようになりました。
- 変更点:
これらの変更は、Go言語の初期のチュートリアルが、言語の基本的な概念をより正確かつ網羅的に伝えるための継続的な努力の一環であったことを示しています。
関連リンク
- Go言語公式ドキュメント: https://go.dev/doc/
- A Tour of Go: https://go.dev/tour/
- Effective Go: https://go.dev/doc/effective_go
参考にした情報源リンク
- Go言語の公式ドキュメントおよびチュートリアル
- Go言語に関する一般的なプログラミング知識
- コミット履歴と差分情報
- (必要に応じて)Go言語の
new
とmake
、インターフェース、並行処理に関するWeb上の解説記事