[インデックス 1456] ファイルの概要
このコミットは、Go言語のチュートリアル (doc/go_tutorial.txt
) を、当時の最新の言語仕様に合わせて更新するものです。特に、Go言語における「出力(Printing)」と「メモリ割り当て(Allocation)」に関する新しいセクションが追加され、既存のコード例も新しい言語機能や慣用的な書き方に合わせて修正されています。
コミット
commit 40d5435278e4c7de42c7067244df03be0befc5d6
Author: Rob Pike <r@golang.org>
Date: Fri Jan 9 15:16:31 2009 -0800
update tutorial to new language.
add a section on printing
add a section on allocation
R=rsc
DELTA=500 (278 added, 15 deleted, 207 changed)
OCL=22381
CL=22456
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/40d5435278e4c7de42c7067244df03be0befc5d6
元コミット内容
このコミットは、Go言語のチュートリアルと関連するサンプルコードを、言語の進化に合わせて大幅に更新しています。主な変更点は以下の通りです。
- チュートリアル (
doc/go_tutorial.txt
) の内容が、2009年1月9日時点のGo言語の仕様に更新されました。 - 「Printing」に関する新しいセクションが追加され、
fmt
パッケージの利用方法(printf
,print
,println
など)が詳細に解説されています。 - 「Allocation」に関する新しいセクションが追加され、
new()
とmake()
の違いと使い分けが説明されています。 - 既存のサンプルプログラム (
doc/progs/*.go
) が、新しい言語機能、特にエラーハンドリング、スライス、インターフェース、パッケージのインポート方法に合わせて修正されました。 doc/prog.sh
スクリプトが更新され、新しいサンプルプログラムの実行と出力の検証が含まれるようになりました。
変更の背景
このコミットが行われた2009年1月は、Go言語がまだ活発に開発されていた初期段階にあたります。言語仕様は頻繁に変更されており、チュートリアルやサンプルコードもそれに合わせて更新する必要がありました。特に、以下の点が背景として考えられます。
- 言語の安定化と慣用句の確立:
print()
のような初期のデバッグ用関数から、より堅牢で柔軟なfmt
パッケージへの移行は、言語の標準ライブラリが成熟し、より実用的な出力メカニズムが提供されるようになったことを示しています。 - メモリ管理の明確化:
new()
とmake()
の導入と使い分けの明確化は、Go言語のメモリ割り当てモデルをより理解しやすくするための重要なステップです。特に、スライス、マップ、チャネルといった組み込みの参照型が、どのように初期化され、メモリ上で扱われるかを明確にする必要がありました。 - エラーハンドリングの標準化:
errno
のようなC言語スタイルのエラーコードから、*os.Error
というGo独自の多値戻り値とインターフェースに基づいたエラーハンドリングへの移行は、より統一的でGoらしいエラー処理パターンを確立するためのものです。 - スライスの重要性の認識: 配列よりも柔軟で強力なスライスの概念が、Go言語のデータ構造として中心的な役割を果たすようになり、チュートリアルでもその重要性が強調されるようになりました。
- パッケージ管理と命名規則の進化: パッケージのインポートにおける命名規則の変更(明示的なエイリアスから暗黙的なパッケージ名へ)や、インターフェースのメソッド名の慣習(小文字から大文字へ)は、Goコミュニティ内でのコーディング規約が形成されつつあったことを示唆しています。
これらの変更は、Go言語がより成熟し、開発者がより効率的かつ安全にコードを書けるようにするための基盤を築くものでした。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の基本的な概念と、当時のプログラミング言語の一般的な知識が必要です。
- Go言語の基本構文:
- パッケージ (Package): Goのコードはパッケージにまとめられます。
import
文で他のパッケージを利用します。 - 関数 (Function):
func
キーワードで定義されます。多値戻り値が可能です。 - 変数宣言:
var
キーワードまたは:=
(ショート変数宣言)で変数を宣言します。 - 制御構造:
if
,for
,switch
などの基本的な制御フロー。Goにはwhile
やdo-while
がなく、for
が唯一のループ構文です。 - コメント:
/* ... */
と// ...
。
- パッケージ (Package): Goのコードはパッケージにまとめられます。
- 型システム:
- 組み込み型:
int
,float
,string
,bool
など。 - 構造体 (Struct): 複数のフィールドをまとめた複合型。
- 配列 (Array): 固定長で同じ型の要素のシーケンス。Goの配列は値型です。
- スライス (Slice): 可変長で同じ型の要素のシーケンス。配列の一部を参照するもので、参照型です。Goで最も頻繁に使われるシーケンス型です。
- マップ (Map): キーと値のペアを格納するハッシュテーブル。参照型です。
- ポインタ (Pointer): 変数のメモリアドレスを指す型。
- 組み込み型:
- インターフェース (Interface):
- Goのインターフェースは、メソッドのシグネチャの集合を定義します。
- 型がインターフェースのすべてのメソッドを実装していれば、そのインターフェースを「暗黙的に」実装しているとみなされます。
implements
キーワードのような明示的な宣言は不要です。 - 空インターフェース (
interface{}
): 任意の型の値を保持できるインターフェース。JavaのObject
型に似ています。
- 並行処理 (Concurrency):
- ゴルーチン (Goroutine): Goの軽量な並行実行単位。
go
キーワードで関数をゴルーチンとして実行します。 - チャネル (Channel): ゴルーチン間で値を安全に送受信するための通信メカニズム。参照型です。
- ゴルーチン (Goroutine): Goの軽量な並行実行単位。
- 標準ライブラリ:
os
パッケージ: オペレーティングシステムとのインタフェースを提供します。ファイルI/O、環境変数、コマンドライン引数など。fmt
パッケージ: フォーマットされたI/O(出力)を提供します。printf
,print
,println
など。flag
パッケージ: コマンドライン引数のパースをサポートします。syscall
パッケージ: 低レベルのシステムコールへのアクセスを提供します。io
パッケージ: I/Oプリミティブを提供します。io.Writer
インターフェースなど。
- C言語との比較: Go言語はC言語の影響を受けていますが、多くの点で異なる設計思想を持っています。特に、メモリ管理(ガベージコレクション)、エラーハンドリング、並行処理のモデルが異なります。このチュートリアルでは、C言語の知識がある読者を意識した記述が見られます。
技術的詳細
このコミットは、Go言語の初期の設計思想と、それがどのように進化していったかを示す貴重なスナップショットです。
-
出力メカニズムの進化 (
print()
からfmt
へ):- 初期のGo言語には、デバッグ用途の
print()
組み込み関数が存在しました。これはシンプルですが、フォーマット機能に乏しく、将来的な保証がないとされていました。 - このコミットでは、より強力で柔軟な
fmt
パッケージが導入されます。fmt.Printf
はC言語のprintf
に似ていますが、Goの型システムとリフレクションを活用することで、よりスマートなフォーマットが可能です。例えば、%d
は整数型であればそのサイズや符号に関わらず適切に処理され、%v
は任意の値を適切な形式で出力します。 String()
メソッドを型に実装することで、fmt
パッケージの出力関数がその型をカスタムフォーマットできる仕組みも導入されました。これはGoのインターフェースの強力な応用例です。fprintf
がio.Writer
インターフェースを受け入れるようになったことで、ファイルだけでなく、ネットワーク接続、バッファ、カスタムの変換器など、任意の書き込み可能なストリームに出力できるようになり、I/Oの汎用性が大幅に向上しました。
- 初期のGo言語には、デバッグ用途の
-
メモリ割り当ての明確化 (
new()
vsmake()
):- Go言語では、ほとんどの型は値型であり、変数を宣言するとスタックに割り当てられます。
new(T)
は、型T
のゼロ値へのポインタをヒープに割り当てて返します。これはC++のnew T()
に似ていますが、コンストラクタは呼び出されません。make(T)
は、スライス、マップ、チャネルというGoの組み込み参照型に特化した関数です。これらの型は、単にメモリを割り当てるだけでなく、内部構造を適切に初期化する必要があります。make
は、これらの型を初期化し、使用可能な状態にして返します。new(map[string]int)
はnil
マップへのポインタを返しますが、make(map[string]int)
はすぐに使える空のマップを返します。- この区別は、Goのメモリモデルと参照セマンティクスを理解する上で非常に重要です。
-
エラーハンドリングの改善:
- 以前は、Unixの
errno
のような整数値でエラーが返されることがありました。 - このコミットでは、
*os.Error
というGoの標準的なエラーインターフェースが導入されます。os.ErrnoToError
のようなヘルパー関数を使って、低レベルのエラーコードをよりGoらしいエラーオブジェクトに変換します。 - これにより、エラー処理が一貫性を持つようになり、エラーを文字列として表現したり、特定の型のアサーションを行ったりすることが容易になります。
- 以前は、Unixの
-
スライスの中心的な役割:
- Goの配列は固定長の値型ですが、スライスは可変長で、基になる配列の一部を参照する参照型です。
- このコミットでは、スライスがGoプログラムで配列よりもはるかに一般的で柔軟なデータ構造であることが強調されています。関数に配列を渡す際も、自動的にスライス参照が作成され渡されることが説明されています。
[...]int{1,2,3}
のように...
を使うことで、コンパイラに配列のサイズを推論させることができます。
-
インターフェースの柔軟性:
- Goのインターフェースは、型が特定のメソッドセットを実装していれば、そのインターフェースを自動的に満たすという「ダックタイピング」的な性質を持っています。
- このコミットでは、
fd.FD
型がReader
インターフェースを実装する例や、sort
パッケージのSortInterface
の例を通じて、インターフェースがどのようにコードの再利用性と柔軟性を高めるかが示されています。 - メソッドは構造体だけでなく、任意の名前付き型(例えば、
IntArray
のようなスライス型)に対しても定義できることが示され、Goの型システムの強力さが強調されています。
-
パッケージインポートの慣習:
import FD "fd"
のような明示的なパッケージエイリアスは、名前の衝突を解決する場合にのみ必要であり、通常はimport "fd"
のようにパッケージ名がそのまま識別子として使われる慣習が確立されました。
これらの変更は、Go言語がその後のバージョンで採用する多くの設計原則の基礎を築いたものであり、言語の使いやすさ、堅牢性、そしてパフォーマンスを向上させる上で不可欠なものでした。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。
doc/go_tutorial.txt
: チュートリアルの本文。Go言語の新しい機能や慣用句に関する説明が追加・修正されています。特に「Printing」と「Allocation」のセクションが新規追加されました。doc/progs/*.go
: チュートリアルで参照されるGo言語のサンプルプログラム群。cat.go
,cat_rot13.go
,echo.go
,helloworld2.go
,helloworld3.go
,fd.go
,sort.go
,sortmain.go
,sum.go
などが修正されています。print.go
,printf.go
,print_string.go
が新規追加されています。
doc/prog.sh
: チュートリアル内のコード例を抽出・実行するためのシェルスクリプト。新しいサンプルプログラムの追加と、出力フォーマットの調整が行われています。
具体的なコード変更の例をいくつか挙げます。
doc/go_tutorial.txt
からの抜粋(変更点の説明):
--- a/doc/go_tutorial.txt
+++ b/doc/go_tutorial.txt
@@ -45,22 +45,24 @@ Go is defined to accept UTF-8 input. Strings are arrays of bytes, usually used
to store Unicode strings represented in UTF-8.
The built-in function "print()" has been used during the early stages of
-development of the language but is not guaranteed to last. Here's a better version of the
+development of the language but is not guaranteed to last. Here's a version of the
program that doesn't depend on "print()":
--PROG progs/helloworld2.go
This version imports the ''os'' package to acess its "Stdout" variable, of type
-"*OS.FD". The "import" statement is a declaration: it names the identifier ("OS")
+"*os.FD". The "import" statement is a declaration: it names the identifier ("os")
that will be used to access members of the package imported from the file ("os"),\n found in the current directory or in a standard location.\n-Given "OS.Stdout" we can use its "WriteString" method to print the string.\n+Given "os.Stdout" we can use its "WriteString" method to print the string.\n
The comment convention is the same as in C++:
/* ... */
// ...
+Later we'll have much more to say about printing.
+
Echo
----
この部分では、print()
関数の非推奨化と、os
パッケージを使ったより適切な出力方法への移行が示されています。また、パッケージのインポート名が慣習的に小文字になることが示唆されています。
doc/progs/fd.go
からの抜粋(エラーハンドリングと構造体初期化の変更):
--- a/doc/progs/fd.go
+++ b/doc/progs/fd.go
@@ -4,21 +4,21 @@
package fd
-import Syscall "syscall"
+import (
+ "os";
+ "syscall";
+)
export type FD struct {
- fildes int64; // file descriptor number
- name string; // file name at Open time
+ fildes int64; // file descriptor number
+ name string; // file name at Open time
}
func NewFD(fd int64, name string) *FD {
if fd < 0 {
return nil
}
- n := new(FD);
- n.fildes = fd;
- n.name = name;
- return n
+ return &FD{fd, name}
}
export var (
@@ -27,36 +27,36 @@ export var (
Stderr = NewFD(2, "/dev/stderr");
)
-export func Open(name string, mode int64, perm int64) (fd *FD, errno int64) {
- r, e := Syscall.open(name, mode, perm);
- return NewFD(r, name), e
+export func Open(name string, mode int64, perm int64) (fd *FD, err *os.Error) {
+ r, e := syscall.open(name, mode, perm);
+ return NewFD(r, name), os.ErrnoToError(e)
}
-func (fd *FD) Close() int64 {
+func (fd *FD) Close() *os.Error {
if fd == nil {
- return Syscall.EINVAL
+ return os.EINVAL
}
- r, e := Syscall.close(fd.fildes);
+ r, e := syscall.close(fd.fildes);
fd.fildes = -1; // so it can't be closed again
- return 0
+ return nil
}
-func (fd *FD) Read(b []byte) (ret int64, errno int64) {
+func (fd *FD) Read(b []byte) (ret int, err *os.Error) {
if fd == nil {
- return -1, Syscall.EINVAL
+ return -1, os.EINVAL
}
- r, e := Syscall.read(fd.fildes, &b[0], int64(len(b)));
- return r, e
+ r, e := syscall.read(fd.fildes, &b[0], int64(len(b)));
+ return int(r), os.ErrnoToError(e)
}
-func (fd *FD) Write(b []byte) (ret int64, errno int64) {
+func (fd *FD) Write(b []byte) (ret int, err *os.Error) {
if fd == nil {
- return -1, Syscall.EINVAL
+ return -1, os.EINVAL
}
- r, e := Syscall.write(fd.fildes, &b[0], int64(len(b)));
- return r, e
+ r, e := syscall.write(fd.fildes, &b[0], int64(len(b)));
+ return int(r), os.ErrnoToError(e)
}
-func (fd *FD) Name() string {
+func (fd *FD) String() string {
return fd.name
}
この差分は、fd
パッケージがos
パッケージをインポートし、エラーの戻り値がint64
から*os.Error
に変更されたことを示しています。また、NewFD
関数で構造体リテラル&FD{fd, name}
が使われるようになり、Name()
メソッドがString()
メソッドに改名されました。
コアとなるコードの解説
このコミットのコアとなる変更は、Go言語のチュートリアルとそれに付随するサンプルコードが、言語の進化に合わせてどのように更新されたかを示すものです。
-
doc/go_tutorial.txt
の更新:print()
の非推奨化とfmt
パッケージの導入: チュートリアルは、初期のデバッグ用print()
関数が将来的に削除される可能性に言及し、代わりにos.Stdout.WriteString
や、新しく導入されたfmt
パッケージの関数(fmt.Printf
,fmt.Print
,fmt.Println
)を使用することを推奨しています。fmt
パッケージは、C言語のprintf
に似た強力なフォーマット機能を提供し、Goの型システムとリフレクションを活用して、より柔軟な出力が可能です。特に、%v
フォーマット指定子による汎用的な値の出力や、String()
メソッドを実装することでカスタム型を整形して出力できる機能が強調されています。new()
とmake()
の明確な区別: 新しい「An Interlude about Allocation」セクションでは、new()
がメモリを割り当ててゼロ値を返すのに対し、make()
がスライス、マップ、チャネルといった組み込みの参照型を初期化するために使用されることが詳細に説明されています。この区別は、Goのメモリ管理と参照セマンティクスを理解する上で非常に重要です。- スライスの重要性: チュートリアルは、Goの配列が値型であるのに対し、スライスがより柔軟で参照セマンティクスを持つことを強調しています。関数に配列を渡す際も、Goが自動的に効率的なスライス参照を作成して渡すことが説明されています。
- エラーハンドリングの標準化:
errno
のような整数値のエラーコードから、*os.Error
というGoの標準的なエラーインターフェースへの移行が説明されています。これにより、エラー処理が一貫性を持つようになります。 - インターフェースの柔軟性: 型がインターフェースのメソッドを実装していれば、そのインターフェースを暗黙的に満たすというGoのインターフェースの強力な特性が、
Reader
インターフェースやsort.SortInterface
の例を通じて示されています。また、メソッドが構造体だけでなく、任意の名前付き型に定義できることも強調されています。 :=
演算子のスコープ::=
演算子が関数内でのみ使用可能であり、トップレベルでは使用できないことが明記されました。- 制御フローの構文:
for
,if
,switch
ステートメントのブロックに波括弧が必須であること、switch
の各case
に暗黙的なbreak
があることなどが明確化されました。
-
サンプルプログラムの修正:
- パッケージインポートの慣習: 多くのファイルで、
import FD "fd"
のような明示的なエイリアスがimport "fd"
のように、パッケージ名がそのまま識別子として使われる慣習に修正されています。 - エラーハンドリングの更新:
fd.go
やhelloworld3.go
などでは、エラーの戻り値がint64
から*os.Error
に変更され、os.ErrnoToError
関数が使用されています。 - 構造体リテラルの利用:
fd.go
のNewFD
関数では、new(FD)
でポインタを割り当ててからフィールドを個別に設定する代わりに、&FD{fd, name}
という構造体リテラルを使って簡潔に初期化するようになりました。 - メソッド名の変更:
fd.Name()
がfd.String()
に変更され、sort
パッケージのインターフェースメソッドもlen()
,less()
,swap()
からLen()
,Less()
,Swap()
へと、Goの慣習に従って大文字で始まるように変更されました。 - スライス型の直接利用:
sort.go
では、IntArray
などのソート対象の型が、struct { data *[]int }
のような構造体ではなく、直接[]int
のようなスライス型として定義されるようになりました。これにより、コードがより簡潔になり、スライスの柔軟性が強調されます。 flag
パッケージの利用:flag.Bool
で定義されたフラグの値へのアクセスが、n_flag.BVal()
から*n_flag
(ポインタのデリファレンス)に変更されました。- チャネルの作成:
sieve.go
やserver.go
関連のコードで、チャネルの作成にnew(chan int)
ではなくmake(chan int)
が使われるようになりました。
- パッケージインポートの慣習: 多くのファイルで、
これらの変更は、Go言語が初期の実験段階から、より洗練された実用的な言語へと進化していく過程を示しており、現在のGo言語の基盤となる多くの設計原則がこの時期に確立されたことがわかります。
関連リンク
- Go言語公式サイト: https://go.dev/
- Go言語のチュートリアル (現在のバージョン): https://go.dev/tour/welcome/1
- Go言語の仕様 (現在のバージョン): https://go.dev/ref/spec
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語の初期のメーリングリストや設計に関する議論(当時の情報源を特定することは困難ですが、コミットメッセージやコードの変更履歴から推測)
- Go言語の歴史に関する記事や書籍
- Go言語の
fmt
パッケージのドキュメント - Go言語の
os
パッケージのドキュメント - Go言語の
new
とmake
に関する解説記事