Effective Go¶
イントロダクション¶
Goは新しい言語です。既存の言語からいろいろなアイディアを借りてきていますが、効果的なGoプログラムは、関連するような言語のプログラムとはかなり異なる性質を持っています。C++やJavaなどのプログラムをそのままGoに変換しても、満足できるような結果にはなりません。所詮はJavaで書かれたプログラムであって、Goらしいプログラムにはなりません。一方、Goの視点からプログラムについて考えてみると、うまくいく可能性はありますが、既存のプログラムとは違う結果になるでしょう。言い換えると、Goらしいプログラムをうまく書くためには、Goの特性やイディオムを理解することが重要になります。また、命名規則、フォーマット、プログラムの構造などの、Goでプログラミングをするための適切な習慣を知ることも大切になります。そうすることで、他のGoプログラマが簡単にあなたのプログラムを読むことができるようになります。
このドキュメントは明快で、Goらしいコードを書くためのティップスを提供します。このドキュメントは言語仕様と、チュートリアルの知識を補完するものです。先にその両方のドキュメントを読んでください。
サンプル¶
Goのパッケージのソースは、機能を提供するコアライブラリというだけではなく、Goを使用するためのサンプルにもなっています。問題に対して、どのようにアプローチをしようか疑問をもったり、これはどのように実装されているのか疑問を持った場合には、パッケージのソースを読むとその答え、アイディア、背景などが分かるでしょう。
フォーマッティング¶
フォーマットの問題は、よく議論を呼びますが、それほど重要な問題ではありません。開発者は普段と違うフォーマットのスタイルにも適応することはできますが、すべての開発者が同じスタイルを守っていれば、この議論に消費される時間を減らすことができます。この話題に時間をかけずに済むなら、そちらの方が望ましいでしょう。この場合に問題となるのは、規範となる、長いスタイルガイドを使わないで、このようなユートピアに近づいていくにはどのようにすればいいのか、ということです。
Goの設計では、珍しい解決策を取りました。ほとんどのフォーマットに関する問題を自動でやらせるようにしました。 gofmt というプログラムがあり、これはGoのプログラムを読み込んで、インデントと垂直方向の整列、必要ならコメントの再フォーマットも行って、標準的なフォーマットに書き直したソースを出力します。あたらしくレイアウトを行う場面で、 gofmt を実行したときに、結果が正しくないと思われたら、プログラムを修正するか、バグを報告してください。そのまま行わないでください。
サンプルとして、時間を使うのがもったいない、フィールドのコメントの行構造の修正を行ってみます。 gofmt が行ってくれることを見てみます。以下のような宣言文を渡します:
type T struct {
name string; // オブジェクト名
value int; // オブジェクトの値
}
gofmt がカラムの行あげを行ってくれます:
type T struct {
name string; // オブジェクトの名前
value int; // オブジェクトの値
}
ライブラリのすべてのコードは、 gofmt を使ってフォーマット修正しています。
いくつかフォーマットに関する詳細が残っているので、手短に説明します。
- インデント
- Goではインデントにタブを使用します。 gofmt もデフォルトではそのようなコードを出力します。スペースを使用しなければならない場合だけ、使用してください。
- 行の長さ
- Goでは行の長さの制限はありません。パンチカードのオーバーフローを心配する必要はありません。もしも長すぎると感じた場合には、改行して、追加のタブを入れてください。
- カッコ
Goはカッコを打つ回数が少なくて住むようになっています。文法上は制御構文(if, for , switch)も、カッコが不要です。また、演算子の優先順位の階層短くて明確なため:
x<<8 + y<<16
スペースが暗示しているとおりの順番になります。
コメント¶
GoはCスタイルの /* */ ブロックコメントとC++スタイルの // 行コメントを提供します。行コメントは一般的に使用されるもので、ブロックコメントは大抵はパッケージのコメントでよく使われ、大部分のコードを一気に無効にする際にも便利です。
プログラム、そしてWebサーバーでもあるgodocはGoソースファイルを処理してパッケージ内コンテンツのドキュメントを抽出します。トップレベルの宣言前にあり改行が間に入らないコメントはその宣言とともに抽出され、その項目の説明文となります。これらのコメントの特性とスタイルがgodocが生成するドキュメントの質を決定します。
各パッケージはパッケージ節に先行するブロックコメントであるパッケージコメントを持つべきです。パッケージが複数のファイルにわたる場合はそのうち1つのファイルにしか必要ありません。パッケージコメントはそのパッケージの紹介と関連情報をまとめて提供するものであるべきです。これはgodocページの始めに表示されるので、それに続く詳細なドキュメントのお膳立てすべきです。
/*
The regexp package implements a simple library for
regular expressions.
The syntax of the regular expressions accepted is:
regexp:
concatenation { '|' concatenation }
concatenation:
{ closure }
closure:
term [ '*' | '+' | '?' ]
term:
'^'
'$'
'.'
character
'[' [ '^' ] character-ranges ']'
'(' regexp ')'
*/
package regexp
パッケージがシンプルなものであれば、パッケージコメントはシンプルなもので良いでしょう。
// The path package implements utility routines for
// manipulating slash-separated filename paths.
コメントはアスタリスクのバナーのような余分なフォーマッティングをする必要はありません。生成される出力は等幅フォントで表示されないかもしれませんので、位置合わせのためのスペーシングに依存することがないようにします。godocはgofmtのようにこの問題の面倒をみてくれます。最後に、コメントはインタープリタに処理されないプレインテキストなので、HTMLや_このような_註釈は文字どおりに表示されるので使用してはいけません。
パッケージ内でトップレベルの宣言の直前にあるものはその宣言のDocコメントとなります。エクスポートされた(大文字で始まる)名前はdocコメントを持つべきです。
Docコメントは完全な英文で書くのが最も効果的です。これにより広範囲にわたる自動プレゼンテーションが可能となります。始めの文はこれから宣言される名前から始まる1行の要約であるべきです。
// Compile parses a regular expression and returns, if successful, a Regexp
// object that can be used to match against text.
func Compile(str string) (regexp *Regexp, error os.Error) {
Goの宣言構文は宣言のグループ化が可能です。1つのDocコメントはある定数や変数のグループに対し使用することが出来ます。すべての宣言が提示されるので、そのようなコメントは形式的なものとなります。
// Error codes returned by failures to parse an expression.
var (
ErrInternal = os.NewError("internal error");
ErrUnmatchedLpar = os.NewError("unmatched '('");
ErrUnmatchedRpar = os.NewError("unmatched ')'");
...
)
プライベートな名前に対してもグルーピングをしてそれらの関係、たとえばある変数群がmutexでで保護されているなどを示すことが可能です。
var (
countLock sync.Mutex;
inputCount uint32;
outputCount uint32;
errorCount uint32;
)
名前¶
他の言語と同様に、Goにおいても名前は重要です。場合によって、名前は意味上の効果をもたらします。例えば、パッケージの外側にある名前の可視性は、名前の一文字目が大文字かどうかで決定されます。従って、命名規則にわずかばかりの時間を使うことは、Goにおいて価値のあることです。
パッケージ名¶
パッケージをインポートすると、パッケージ名はパッケージ内容へのアクセッサになります。次のように:
import "bytes"
と書くと、インポートされるパッケージを通してbytes.Bufferを使用することができます。パッケージを使用する人々が、パッケージ内容を参照する際に同じ名前を使用することができれば、それは役に立ちます。このことは、パッケージ名が短く、簡潔で、意味明瞭なものほど良いことを意味します。規約により、パッケージには小文字の1単語である名前が与えられます。アンダースコアの使用や大文字小文字の混在は必要ありません。また、あなたのパッケージを使う人々が使う度にパッケージ名を打ち込むことを考えて、パッケージ名を簡潔過ぎるほど簡潔にしてしまう場合があります。その場合でも、名前の事前衝突を心配する必要はありません。なぜなら、パッケージ名はインポートするためのデフォルト名でしかないからです。すなわち、ソースコード全体でパッケージ名がユニークである必要はありません。万が一インポートされるパッケージ名が衝突する場合にも、局所的に異なるパッケージ名を選択することが可能です。どのような場合でも、インポート機能において、ファイル名がどのパッケージが使用されているかを決定するので、混乱することはまれです。
もう一つの規約は、パッケージ名はそのソースファイルのディレクトリの基本名であるということです。src/pkg/container/vectorパッケージは、”container/vector”としてインポートされます。パッケージ名はvectorであって、container_vectorでもcontainerVectorでもありません。
パッケージインポータは、パッケージ内容を参照するためにパッケージ名を使用します(.(ドット)記法を使ったインポートは、テストや他のまれな場面で使用します)。
もう一つの短い例は、once.Doです。onche.Do(setup)は読みやすく、once.DoOrWaitUntileDone(setup)と書いてもより読みやすくなることはありません。より長い名前を使えば、より読みやすくなるということはありません。もし名前が難解であったり、捉えにくい場合、名前にすべての情報を詰め込もうとするより、役に立つDocコメントを記述するのがより良いのが普通です。
インターフェース名¶
規約により、1メソッドのインターフェースは、メソッド名に-erサフィックスを加えた名前を持ちます。例えば、Reader, Writer, Formatter などです。
このような名前は多く存在します。そして、その名前と、それらが得る機能名は、敬意を表するほど生産的です。Read, Write, Close, Flush, String などの名前は、標準的な特徴と意味を備えています。混乱を避けるため、あなたのメソッドが同じ特徴と意味を備えていない限り、そのような名前をメソッド名としてはなりません。逆に言えば、もしあなたがよく知られた種類の、同じ意味を持つメソッドを実装したならば、同じ名前と特徴を与えるべきです。例えば独自の文字列変換メソッドを実装するなら、そのメソッド名はToStringではなく、Stringであるべきです。
大文字小文字の混在¶
最後に、Goにおける規約では、複数単語の名前を記述する際、アンダースコアを使用するのではなく、MixedCapsまたはmixedCapsと記述するように決められています。
セミコロン¶
Goは他のCの異型のように多くのセミコロンを必要としません。セミコロンはトップレベルでは使う必要はありません。また終端記号ではなく分離記号なので、文や宣言のリストの最後では省くことが可能で、一行の関数などでの使用に便利です。
func CopyInBackground(dst, src chan Item) {
go func() { for { dst <- <-src } }()
}
事実、セミコロンは文法上のいかなる”文のリスト”でも省略することが可能です。これはswitch文でのcaseを含みます。
switch {
case a < b:
return -1
case a == b:
return 0
case a > b:
return 1
}
文法上、すべての文のリストのあとに空文を許されているので、終端にセミコロンを使ってもかまいません。結果としてCでセミコロンを使う場所で使っても問題ありませんし、たとえばこれらのreturnの後に置いてもかまいませんが、通常は省略されます。慣習上、セミコロンはトップレベルの宣言では省略されます(たとえばstructやfuncの宣言の閉じ括弧にセミコロンは使われません)し、ワンライナーでもそうですが、関数の中では適切だと思うところで使ってください。
制御構造¶
Goの制御構造はCのものと関係がありますが、重要な点で異なります。doやwhileループがなく、少々一般化されたforだけがあり、switchはより柔軟で、ifやswitchではforのように任意の初期化文を使え、そしてtype switchやmultiway communications multiplexerやselectなどの新規の制御構造があります。文法も少し異なります。括弧が必須ではなく、本文は必ず中括弧で囲まれていなければなりません。
If¶
Goでは単純なifは次のようなものです。
if x > 0 {
return y
}
中括弧を強制することにより単純なif文を複数行にわたって書くよう促します。これは良いスタイルですが、本体にreturnやbreakなどの制御文が含まれる場合は特にそうです。
ifやswitchは初期化文を使えるので、ローカル変数をセットアップする際によく使われます。
if err := file.Chmod(0664); err != nil {
log.Stderr(err);
return err;
}
Goのライブラリでは、if文で次の文に処理が進まない時、つまり本体がbreakやcontinue、goto、returnなどで終わる時、不要であるelseは省略されます。
f, err := os.Open(name, os.O_RDONLY, 0);
if err != nil {
return err;
}
codeUsing(f);
これはエラーの発生を順にチェックしなければならない場合によく見られる例です。正常系のフローはページを下がっていくもので、エラーケースは発生のたびに打ち切られるようなコードは読みやすいものです。この場合エラーケースは通常return文で終わるので、結果としてelse文は不要となります。
f, err := os.Open(name, os.O_RDONLY, 0);
if err != nil {
return err;
}
d, err := f.Stat();
if err != nil {
return err;
}
codeUsing(f, d);
For¶
GoのforはCのものと似ていますが同じではありません。それはforとwhileを1つにしたものでdo-whileはありません。forは3つの形式があり、そのうち1つだけがセミコロンを使います。
// Cのforのようなもの
for init; condition; post { }
// Cのwhileのようなもの
for condition { }
// Cのfor(;;)のようなもの
for { }
宣言が短いのでインデックス変数をループの中で簡単に宣言出来ます。
sum := 0;
for i := 0; i < 10; i++ {
sum += i
}
配列、スライス、マップなどをループする時やチャンネルから読み込む時にはrange節を使ってループをうまく使うことが出来ます。
var m map[string]int;
sum := 0;
for _, value := range m { // キーは未使用
sum += value
}
文字列に対してはrangeはなお便利です。ユニコード文字列をUTF-8に分けて分割してくれます。(間違ったエンコーディングは1バイトを使って代わりにU+FFFDを出力します)
for pos, char := range "日本語" {
fmt.Printf("character %c starts at byte position %d\n", char, pos)
}
次を出力します。
character 日 starts at byte position 0
character 本 starts at byte position 3
character 語 starts at byte position 6
最後に、Goはカンマ演算子を持たず、++や–は文であり式ではないため、複数の変数をfor内で使用したい場合は並列代入を使わなければなりません。
// aを逆転させる
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
Switch¶
GoのswitchはCのものより雑多なものです。式は定数である必要はありませんし、整数ですらなくてもかまいません。caseは一致が見つかるまで上から順に評価されます。もしswitchが式を持っていなければ、真となるcaseに切り替えられます。よってswitchをif-else-if-elseのチェインをswitchで書くことが出来ますし、慣習的なものでもあります。
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
自動的にフォールスルーは発生しませんが、caseをカンマ区切りのリストで表現することが出来ます。
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}
次に2つのswitch文を使ったバイト列の比較関数をお見せします。
// Compareは2つのバイト列を辞書順で比較した結果を整数として返します。
// a == bの場合は0、a < bの場合は-1、a > bの場合は+1を返します。
func Compare(a, b []byte) int {
for i := 0; i < len(a) && i < len(b); i++ {
switch {
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
}
}
switch {
case len(a) < len(b):
return -1
case len(a) > len(b):
return 1
}
return 0
}
switchはインタフェース変数の動的な型を調べる時にも使われます。こういったtype switchはtypeキーワードを使った型アサーションを括弧の中で使用します。switchが変数を式の中で宣言するとその変数はその節で対応する型として扱われます。
switch t := interfaceValue.(type) {
default:
fmt.Printf("unexpected type %T", type); // %T prints type
case bool:
fmt.Printf("boolean %t\n", t);
case int:
fmt.Printf("integer %d\n", t);
case *bool:
fmt.Printf("pointer to boolean %t\n", *t);
case *int:
fmt.Printf("pointer to integer %d\n", *t);
}
関数¶
複数の戻り値¶
Goでは、関数やメソッドは複数の値を返すことが出来ます。これは珍しい特徴ですが、C言語の(EOFをあらわす-1のような)in-bandエラーの戻り値や、引数の変更といったような醜い構文を改善することが出来ます。
C言語では、書き込みエラーは負の数値で通知され、エラーコードはどこかに隠されてしまいます。G言語では、Writeオブジェクトは、数値と ”デバイスが一杯になったので、データの一部は書き込まれませんでした”というエラーを返します。osパッケージの*File.Writeオブジェクトのシグネチャは以下のようになります。
func (file *File) Write(b []byte) (n int, err Error)
ドキュメントによるとWriteは戻り値として、書き込まれたデータのバイト数と、 もし全てが書き込まれなかった場合(n != len(b)のとき)にはnilでないエラーを返すと書かれています。これは共通のスタイルです。もしもっとたくさんの例を見たければ、エラーのセクションを参照してください。
似たようなアプローチをとることで、参照変数をシミュレートするために戻り値にポインタを渡す必要がなくなります。以下は、バイト配列の中から、指定した添え字の数値とその次の添え字を取り出す簡単な関数です。
func nextInt(b []byte, i int) (int, int) {
for ; i < len(b) && !isDigit(b[i]); i++ {
}
x := 0;
for ; i < len(b) && isDigit(b[i]); i++ {
x = x*10 + int(b[i])-'0'
}
return x, i;
}
これは次のように、入力した配列から数値を探し出すのに使うことができます。
for i := 0; i < len(a); {
x, i = nextInt(a, i);
fmt.Println(x);
}
名前付けされた戻り値¶
Goの関数の”戻り値”は、ちょうど入力値のように、名前をつけ普通の変数として扱うことが出来ます。名前がつけられると、関数が始まるときにそれらの変数は、型に合った初期値で初期化されます。もし、関数が実行された結果値を返さなかったら、その時点での変数の値が戻り値として返されます。
名前は必須ではありませんが、記述することでコードを短く、読みやすく出来ます。ドキュメントには、nextIntの戻り値に名前をつけた例が載っています。
func nextInt(b []byte, pos int) (value, nextPos int) {
名前付けされた戻り値は初期化され、名前付けされていない変数と結び付けられます。これらはとてもシンプルに記述できるだけでなく、分かりやすくすることができます。以下は、io.ReadFullをこれらを上手く用いて書き直したものです。
func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
for len(buf) > 0 && err == nil {
var nr int;
nr, err = r.Read(buf);
n += nr;
buf = buf[nr:len(buf)];
}
return;
}
データ¶
new()によるメモリ割り当て¶
Goはメモリ割り当てプリミティブとしてnew()とmake()の2つを持っています。これらは動作も適用される型も異なっている為、混乱を招くかもしれませんがルールは単純です。まずはnew()から始めましょう。new()は組み込み関数で、他の言語での同名のものと本質的に同じです。new(T)は、T型のゼロで初期化された新しい要素を割り当て、そのアドレスを*T型の値として返します。Goの用語では、new(T)は新しく確保されたT型のゼロ値へのポインタを返します。
new()に返されたメモリはゼロクリアされているため、初期値がゼロで良いようなオブジェクトを使う場合には、特に初期化する事無く使う事ができて便利です。つまり、そのデータ構造は単にnew()するだけできちんと動作する、という事です。例えば、bytes.Bufferのドキュメントには、「ゼロの値をもつバッファは利用可能状態の空のバッファである」と定められています。同様にsync.Mutexも明示的なコンストラクタやInitメソッドを持ちません。代わりに、ゼロの値をもつsync.Mutexは非ロック状態のmutexであると定義されています。
「ゼロによる初期化は有用」という特徴はドミノ倒しに働きます。次の型宣言を考えてみましょう:
type SyncedBuffer struct {
lock sync.Mutex;
buffer bytes.Buffer;
}
SyncedBuffer型の値はnew()によるメモリ割り当てでも、単なる宣言であっても即使えるようになります。次のコード片では特に調整しなくてもp, v両方とも正しく動作します:
p := new(SyncedBuffer); // type *SyncedBuffer
var v SyncedBuffer; // type SyncedBuffer
コンストラクタと複合リテラル¶
時としてゼロ値だけでは不十分で、初期化コンストラクタが必要となる場合もあります。以下の例はosパッケージから派生した物です:
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := new(File);
f.fd = fd;
f.name = name;
f.dirinfo = nil;
f.nepipe = 0;
return f;
}
上のコードはいささか冗長で、複合リテラルを使う事で簡単にできます。複合リテラルは評価されるたびに新しいインスタンスを生成する式です:
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := File{fd, name, nil, 0};
return &f;
}
ここで注意すべき点は、ローカル変数のアドレスを戻り値にするのは完全に合法であり、変数領域は関数が帰った後も保持される、ということです。実のところ、複合リテラルのアドレスを取得すると、その式が評価されるごとに新しいインスタンスが割り当てられるので、最後の2行は1行にまとめる事ができます:
return &File{fd, name, nil, 0};
複合リテラルのフィールドは定義順通りに、かつ漏れなく指定する必要があります。しかしながら、要素を明示的に field:value ペアのように書く事で任意の順序で書く事もできます。また指定しなかったフィールドはゼロに初期化されます。結果、以下のように書く事ができます:
return &File{fd: fd, name: name}
稀なケースとして、全くフィールドを含まない複合リテラルの場合、その型のゼロ値を生成します。new(File)と&File{}は等価です:
複合リテラルは配列やスライス、マップの生成にも使えます。その場合フィールドにつけたラベルは、インデックスかマップのキーになります。以下の例では、Enone, Eio, Einvalが相異なってさえいれば、値に関係なく初期化は動作します:
a := [...]string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"};
s := []string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"};
m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"};
make()によるメモリ割り当て¶
メモリ割り当てに戻りましょう。組み込み関数make(T, args)はnew(T)とは違った目的に使われます。make()はスライス、マップ、チャンネル専用で、初期化(ゼロではありません)したT型(*T型ではありません)を返します。この区別をしている理由は、内部的にはこれら3つのタイプが、使用前に初期化が必要なデータ構造への参照となっているためです。例えばスライスは3要素の記述子で(配列内の)データへのポインタ、長さ、容量を含んでいます。これらが初期化されるまで、スライスはnilです。スライス、マップ、チャンネルでは make は内部構造を初期化して、使用する値を作成します。たとえば:
make([]int, 10, 100)
上記コードは100個のint配列を割り当て、その後、スライスの構造を長さ10で容量100で作成します(スライスを作る際、容量は無視されることがあります。詳細はスライスの節を参照してください)。対照的に new([]int)は新規に割り当てたゼロ値のスライス構造へのポインタ、すなわちnilスライスへのポインタを返します。
次の例はnew(), make()の違いを示しています。
var p *[]int = new([]int); // allocates slice structure; *p == nil; rarely useful
var v []int = make([]int, 100); // v now refers to a new array of 100 ints
// Unnecessarily complex:
var p *[]int = new([]int);
*p = make([]int, 100, 100);
// Idiomatic:
v := make([]int, 100);
make()はマップ、スライス、チャネルのいずれかのみに適用でき、ポインタを返さないことに注意してください。明示的にポインタを取得するにはnew()で割り当てます。
配列¶
配列は、メモリレイアウトの詳細が分かっている場合に有用で、時に割り当てを避けるのに役に立つ事がありますが、もっぱら次の節の題目である、スライスの素材として使われます。そのトピックの基礎を築くために、いくつかの配列についての言明があります。
配列どう動作するか、の点でGoとCとでは大きな違いがあります。Goでは、
- 配列は値です。ある配列を別の配列に代入することは全要素のコピーになります。
- 特に、配列を関数に渡す場合、ポインタではなく配列のコピーを受け取る事になります。
- 配列のサイズは型の一部です。[10]int と [20]int は異なる型となります。
配列が値である、という特性は便利ですが同時に効率が悪くなります。Cのような振る舞いと効率を求めるなら、配列のポインタを渡す事もできます。
func Sum(a *[3]float) (sum float) {
for _, v := range a {
sum += v
}
return
}
array := [...]float{7.0, 8.5, 9.1};
x := sum(&array); // Note the explicit address-of operator
しかし、この書き方もまたGoらしいスタイルではありません。Goらしいと言えばスライスです。
スライス¶
スライスは配列をラップし、連続データへの汎用的・強力かつ便利なインターフェイスを提供します。変換行列のように明示的な次元を持つものを除き、Goでは殆どの配列プログラミングが、単純な配列よりむしろスライスを使って行われます。
スライスは参照型、つまりスライスに別のスライスを代入した場合、双方のスライスは同じ元の配列を指します。例えば、スライスを引数にとる関数の場合、その関数がスライスの要素に行った変更は呼び出し元(caller)にも見えます。これは元の配列のポインタを渡すのと似ています。Read関数は従って、ポインタと要素数を受け取るのではなく、スライスを引数として受ける事ができます。スライス内部の長さはデータ数の上限値に設定されます。次の行はosパッケージ:File型のReadメソッドのシグネチャです。
func (file *File) Read(buf []byte) (n int, err os.Error)
このメソッドはリードしたバイト数および(もしあれば)エラー値を返します。大きなバッファbから最初の32バイトを読むには以下のようにバッファをスライスします。
n, err := f.Read(buf[0:32]);
このようなスライスは一般的で効率が良いです。実際、効率を無視すれば、次のようなコードでも同じ事が可能です。
var n int;
var err os.Error;
for i := 0; i < 32; i++ {
nbytes, e := f.Read(buf[i:i+1]); // Read one byte.
if nbytes == 0 || e != nil {
err = e;
break;
}
n += nbytes;
}
スライスの長さは元の配列の大きさに収まっている限り、単にスライスに代入するだけで自由に変更できます。スライスの容量は、組み込み関数capにてアクセスする事ができます。スライスが仮定している最大の長さをレポートします。スライスにデータを追加する関数です。容量を超えた場合、スライスは再割り当てされます。結果のスライスが戻ります。この関数はlen, capは nilスライスに適用した場合でも 0を返す、ということを利用しています。
func Append(slice, data[]byte) []byte {
l := len(slice);
if l + len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]byte, (l+len(data))*2);
// Copy data (could use bytes.Copy()).
for i, c := range slice {
newSlice[i] = c
}
slice = newSlice;
}
slice = slice[0:l+len(data)];
for i, c := range data {
slice[l+i] = c
}
return slice;
}
スライスをあとから戻すべきです。なぜなら、Appendはスライスの要素を変更するかもしれませんが、スライス自身(ポインタ、長さ、容量を持った実行時データ構造)が値として渡されるからです。
マップ¶
マップは値を別の型に関連づける、便利で強力な組み込みデータ構造です。キーには整数や浮動小数点数、文字列、ポインタ、インターフェイス(動的型が同値をサポートする限り)のように同値演算子が定義されていればどんな型でも使えます。構造体、配列、またスライスは同値が定義されていないので、マップのキーとして使えません。スライスのようにマップは参照型です。そのマップ内部を変更する関数にマップを渡す場合、呼び出し側にも変更は見えます。
マップはコロンで分割したkey-valueペアをつかって普通の複合リテラル文法で構築する事ができます。初期化のときにつくるのも簡単。
var timeZone = map[string] int {
"UTC": 0*60*60,
"EST": -5*60*60,
"CST": -6*60*60,
"MST": -7*60*60,
"PST": -8*60*60,
}
マップの代入と読み出しはインデックスが整数でなくても良いという部分を除くとほぼ配列と同じです。存在しないキーを指定した場合、プログラムはクラッシュします。しかし、並列代入によってこれを安全に行う方法があります。
var seconds int;
var ok bool;
seconds, ok = timeZone[tz]
これは、自明な理由から”comma ok”イディオムと呼ばれます。この例ではtzが存在していればsecondsには該当する値が設定されokにはtrueが、でなければsecondsにはゼロが設定され okには falseが入ります。以下は両者を出力する関数です:
func offset(tz string) int {
if seconds, ok := timeZone[tz]; ok {
return seconds
}
log.Stderr("unknown time zone", tz);
return 0;
}
値を読まずにマップ中の存在をチェックするには、空の識別子、単にアンダースコア(_)、を使います。空の識別子はあらゆる値、あらゆる型を代入/宣言する事ができ、値は安全に破棄する事ができます。マップ中での存在をテストには、空の識別子を値の普通の変数のところに配置します。
_, present := timeZone[tz];
マップのエントリを削除するには、並列代入をひっくり返して、追加の論理値を右に書きます。論理値がfalseならエントリは削除されます。キーが既にマップから削除済みであっても安全に行う事ができます。
timeZone["PDT"] = 0, false; // Now on Standard Time
印字¶
Goの書式付き表示はCのprintfファミリーと似ていますがより機能豊富で一般的です。それらの関数はfmtパッケージの内部にあり、先頭大文字の名前: fmt.Printf, fmt.Sprintf, などとなっています。文字列関数(Sprintfなど)は指定されたバッファに放り込むのではなく、文字列を返します。
書式文字列を指定する必要はありません。Printf, FPrintf, SprintfはそれぞれPrint, Printlnのペアを持っている。これらの関数は書式文字列をとらず、各引数のデフォルトの書式を生成する。lnバージョンは更に文字列でなければ各引数の間にスペースを挿入し、行の最後に改行を追加する
fmt.Printf("Hello %d\n", 23);
fmt.Fprint(os.Stdout, "Hello ", 23, "\n");
fmt.Println(fmt.Sprint("Hello ", 23));
チュートリアルでも触れた通り、fmt.Fprintfとその仲間は第一引数として io.Writer インターフェイスを実装した任意のオブジェクトをとります。変数 os.Stdout, os.Stderr は見慣れた例です。
このあたりからCとの違いが出てきます。第一に%dなどの数値書式は符号やサイズなどのフラグを受け付けません。代わりに表示ルーチンは引数の型からこれらの性質を決定します。
var x uint64 = 1<<64 - 1;
fmt.Printf("%d %x; %d %x\n", x, x, int64(x), int64(x));
表示はこうなります:
18446744073709551615 ffffffffffffffff; -1 -1
整数の場合の十進表示のようにデフォルトの変換だけでよければ、なんでもあり書式 %v (値のv)を使う事ができます。結果はPrintやPrintlnの出力と全く同じになります。さらにこの書式は任意の値、配列や構造体、マップであっても表示できます。次の例は前節でのタイムゾーンマップを表示する文です:
fmt.Printf("%v\n", timeZone); // or just fmt.Println(timeZone);
以下の出力を得ます:
map[CST:-21600 PST:-28800 EST:-18000 UTC:0 MST:-25200]
マップの場合、キーの順序はもちろん任意です。構造体を表示する場合、変更書式 %+v は構造体のフィールドを名前付きで表示するように指示します。代替書式として %#vがあり、これは Goの完全な文法を表示します。
type T struct {
a int;
b float;
c string;
}
t := &T{ 7, -2.35, "abc\tdef" };
fmt.Printf("%v\n", t);
fmt.Printf("%+v\n", t);
fmt.Printf("%#v\n", t);
fmt.Printf("%#v\n", timeZone);
表示は以下の通り:
&{7 -2.35 abc def}
&{a:7 b:-2.35 c:abc def}
&main.T{a:7, b:-2.35, c:"abc\tdef"}
map[string] int{"CST":-21600, "PST":-28800, "EST":-18000, "UTC":0, "MST":-25200}
(アンパサンド&に注意) クォートされたこれらの文字列書式は、対象が文字列型の値か[]byte型の場合 %qでも得る事ができます。別の書式 %#qは可能であればバッククォートをつけます。更に, %xは文字列とバイトおよびint配列で動作し、16進数の長い文字列を生成します。スペース付き書式% xだとバイト区切りにスペースを追加します。
もう一つの便利な書式は %T です。これは値の型を出力します。
fmt.Printf(“%Tn”, timeZone);
表示は以下の通り:
map[string] int
もし、カスタム型について、デフォルトの書式を変更したければ、必要なのはその型についてString()メソッドを定義することです。単純な型 Tについて、以下のようになります。
func (t *T) String() string {
return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c);
}
fmt.Printf("%v\n", t);
書式付きで表示すると以下のようになります:
7/-2.35/"abc\tdef"
printルーチンは完全に再入可能に書かれているので再帰的に利用する事ができます。結果、String()メソッドはSprintfを呼び出す事が可能です。更に進んで表示ルーチンの引数を直接他のそういったルーチンに渡す事すらできます。Printfのシグネチャの最後の引数に ... 型の使う事で、任意の数のパラメータを書式の後に書く事ができる事を示します。
func Printf(format string, v ...) (n int, errno os.Error) {
関数 Printfの中で、v は渡す事のできる変数で、例えば、他の表示ルーチンです。次の例は上で使った log.Stderr 関数の実装です。これはその引数を fmt.Sprintlnに直接実際の書式を送っています。
// Stderr is a helper function for easy logging to stderr. It is analogous to Fprint(os.Stderr).
func Stderr(v ...) {
stderr.Output(2, fmt.Sprintln(v)); // Output takes parameters (int, string)
}
表示についてここで全てを網羅することはできません。詳細についてはfmtパッケージのgodocドキュメントを参照してください。
初期化¶
Goの初期化は一見CやC++と大して変わらないように見えますが、より強力なものです。複雑な構造を初期化中に構築可能で、異なるパッケージ中のオブジェクト間の初期化順序も正しく処理します。
定数¶
Goの定数は文字通り定数です。定数は、たとえ関数でローカル定義されていてもコンパイル時に生成されます。また定数は、数値、文字列、論理値のいずれかでなくてはいけません。コンパイル時という制約から、それらを定義する式はコンパイラが評価できる定数式である必要があります。例えば、 1<<3 は定数式ですが、math.Sin(math.Pi/4)はmath.Sinへの関数呼び出しが実行時に発生する為、定数式ではありません。
Goでは列挙定数はiota列挙子をつかって生成されます。iotaは式の一部にもでき、式は暗黙的に繰り返されるので、複雑な値の集合も簡単に生成できます:
type ByteSize float64
const (
_ = iota; // 空の識別子に割り当てて、最初の値(0)を無視する
KB ByteSize = 1<<(10*iota);
MB;
GB;
TB;
PB;
YB;
)
型にString()などのメソッドを関連づける事で、丁度普通の組み込み型のように自動的にフォーマット表示させる事が可能です:
func (b ByteSize) String() string {
switch {
case s >= YB:
return fmt.Sprintf("%.2fYB", b/YB)
case s >= PB:
return fmt.Sprintf("%.2fPB", b/PB)
case s >= TB:
return fmt.Sprintf("%.2fTB", b/TB)
case s >= GB:
return fmt.Sprintf("%.2fGB", b/GB)
case s >= MB:
return fmt.Sprintf("%.2fMB", b/MB)
case s >= KB:
return fmt.Sprintf("%.2fKB", b/KB)
}
return fmt.Sprintf("%.2fB", b)
}
式YBは1.00YBと表示され、ByteSize(1e13)は9.09TBと表示されます。
変数¶
変数は定数と同じように初期化することも可能ですが、初期化子に実行時に計算される普通の式が使えます:
var (
HOME = os.Getenv("HOME");
USER = os.Getenv("USER");
GOROOT = os.Getenv("GOROOT");
)
init 関数¶
最後に、各ソースファイルは必要なあらゆる状態を設定するために、独自のinit()関数を定義する事ができます。唯一の制約は、初期化中にgoroutineを起動する事はできますが、初期化が完了するまで実行されないという事です。つまり初期化は常にシングルスレッドとして実行されます。また「最後に」と言うのはまさしく「最後」で、init()が呼び出されるのはパッケージ中の全ての変数宣言が初期化子を評価し、全てのimportされているパッケージが初期化された後です。
宣言として表現できない初期化の他に、init()関数の良く使われる用法の一つとしては実際の実行が始まる前にプログラム状態の正当性を検証したり修正することがあります。:
func init() {
if USER == "" {
log.Exit("$USER not set")
}
if HOME == "" {
HOME = "/usr/" + USER
}
if GOROOT == "" {
GOROOT = HOME + "/go"
}
// GOROOT はコマンドラインの--gorootフラグで上書き可能
flag.StringVar(&GOROOT, "goroot", GOROOT, "Go root directory")
}
メソッド¶
ポインタ vs 値¶
メソッドは、ポインタ型・インターフェースでない任意の型で定義することができます。受け側は、必ずしも構造体である必要は有りません。
前述のスライスの議論で、私たちはAppend関数を書きました。私たちは同じものをslicesのメソッドとして定義できます。
これを行うには、まずメソッドを結びつけられる名前付きの型を定義します。そして関数がその型の値を受け取るように定義します。
type ByteSlice []byte
func (slice ByteSlice) Append(data []byte) []slice {
// 関数内の処理は、上記と同じ
}
これはまだ、更新されたスライスを返すメソッドを必要とします。私たちは、受信側としてByteSliceのポインタを取得するようにメソッドを再定義することで、ぎこちなさを排除することができます。そうすることで、メソッドは呼び出し元のスライスを上書きすることが出来ます。
func (p *ByteSlice) Append(data []byte) {
slice := *p;
実は、もっと良くすることも出来ます。私たちの関数は標準のWriteメソッド似ているので、もし変更するならこうなります。
func (p *ByteSlice) Write(data []byte) (n int, err os.Error) {
slice := *p;
// 上記から
*p = slice;
return len(data), nil)
}
*ByteSlice型は標準インタフェースのio.Writerを満たす便利な型です。例えば、例えば、スライスを一つとして表示することができます。
var b ByteSlice;
fmt.Fprintf(&b, "This hour has %d days\n", 7);
io.Writerを満たすのは*ByteSliceだけなので、ByteSliceのアドレスを渡す必要があります。
受け側で、ポインタ型と値のどちらを使うかというと、値を受け取るメソッドは、ポインタ型と値を受け取りますが、ポインタ関数は、ポインタのみ受け取ります。
ポインタメソッドでは、受け側を修正することができます。理由は、値のコピーによる変更の破棄を引き起こす恐れがあるためです。
ところで、バイト単位のスライスをWriteを使って行うというアイデアは、bytes.Bufferで実装しています。
Interfaces and other types¶
Interfaces¶
Interfaces in Go provide a way to specify the behavior of an object: if something can do this, then it can be used here. We’ve seen a couple of simple examples already; custom printers can be implemented by a String method while Fprintf can generate output to anything with a Write method. Interfaces with only one or two methods are common in Go code, and are usually given a name derived from the method, such as io.Writer for something that implements Write.
A type can implement multiple interfaces. For instance, a collection can be sorted by the routines in package sort if it implements sort.Interface, which contains Len(), Less(i, j int) bool, and Swap(i, j int), and it could also have a custom formatter. In this contrived example Sequence satisfies both:
type Sequence []int
// Methods required by sort.Interface.
func (s Sequence) Len() int {
return len(s)
}
func (s Sequence) Less(i, j int) bool {
return s[i] < s[j]
}
func (s Sequence) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Method for printing - sorts the elements before printing.
func (s Sequence) String() string {
sort.Sort(s);
str := "[";
for i, elem := range s {
if i > 0 {
str += " "
}
str += fmt.Sprint(elem);
}
return str + "]";
}
Conversions¶
The String method of Sequence is recreating the work that Sprint already does for slices. We can share the effort if we convert the Sequence to a plain []int before calling Sprint:
func (s Sequence) String() string {
sort.Sort(s);
return fmt.Sprint([]int(s));
}
The conversion causes s to be treated as an ordinary slice and therefore receive the default formatting. Without the conversion, Sprint would find the String method of Sequence and recur indefinitely. Because the two types (Sequence and []int) are the same if we ignore the type name, it’s legal to convert between them. The conversion doesn’t create a new value, it just temporarily acts as though the existing value has a new type. (There are other legal conversions, such as from integer to float, that do create a new value.)
It’s an idiom in Go programs to convert the type of an expression to access a different set of methods. As an example, we could use the existing type sort.IntArray to reduce the entire example to this:
type Sequence []int
// Method for printing - sorts the elements before printing
func (s Sequence) String() string {
sort.IntArray(s).Sort();
return fmt.Sprint([]int(s))
}
Now, instead of having Sequence implement multiple interfaces (sorting and printing), we’re using the ability of a data item to be converted to multiple types (Sequence, sort.IntArray and []int), each of which does some part of the job. That’s more unusual in practice but can be effective.
Generality¶
If a type exists only to implement an interface and has no exported methods beyond that interface, there is no need to export the type itself. Exporting just the interface makes it clear that it’s the behavior that matters, not the implementation, and that other implementations with different properties can mirror the behavior of the original type. It also avoids the need to repeat the documentation on every instance of a common method.
In such cases, the constructor should return an interface value rather than the implementing type. As an example, in the hash libraries both crc32.NewIEEE() and adler32.New() return the interface type hash.Hash32. Substituting the CRC-32 algorithm for Adler-32 in a Go program requires only changing the constructor call; the rest of the code is unaffected by the change of algorithm.
A similar approach allows the streaming cipher algorithms in the crypto/block package to be separated from the block ciphers they chain together. By analogy with the bufio package, they wrap a Cipher interface and return hash.Hash, io.Reader, or io.Writer interface values, not specific implementations.
The interface to crypto/block includes:
type Cipher interface {
BlockSize() int;
Encrypt(src, dst []byte);
Decrypt(src, dst []byte);
}
// NewECBDecrypter returns a reader that reads data
// from r and decrypts it using c in electronic codebook (ECB) mode.
func NewECBDecrypter(c Cipher, r io.Reader) io.Reader
// NewCBCDecrypter returns a reader that reads data
// from r and decrypts it using c in cipher block chaining (CBC) mode
// with the initialization vector iv.
func NewCBCDecrypter(c Cipher, iv []byte, r io.Reader) io.Reader
NewECBDecrypter and NewCBCReader apply not just to one specific encryption algorithm and data source but to any implementation of the Cipher interface and any io.Reader. Because they return io.Reader interface values, replacing ECB encryption with CBC encryption is a localized change. The constructor calls must be edited, but because the surrounding code must treat the result only as an io.Reader, it won’t notice the difference.
Interfaces and methods¶
Since almost anything can have methods attached, almost anything can satisfy an interface. One illustrative example is in the http package, which defines the Handler interface. Any object that implements Handler can serve HTTP requests:
type Handler interface {
ServeHTTP(*Conn, *Request);
}
For brevity, let’s ignore POSTs and assume HTTP requests are always GETs; that simplification does not affect the way the handlers are set up. Here’s a trivial but complete implementation of a handler to count the number of times the page is visited:
// Simple counter server.
type Counter struct {
n int;
}
func (ctr *Counter) ServeHTTP(c *http.Conn, req *http.Request) {
ctr.n++;
fmt.Fprintf(c, "counter = %d\n", ctr.n);
}
(Keeping with our theme, note how Fprintf can print to an HTTP connection.) For reference, here’s how to attach such a server to a node on the URL tree:
import "http"
...
ctr := new(Counter);
http.Handle("/counter", ctr);
But why make Counter a struct? An integer is all that’s needed. (The receiver needs to be a pointer so the increment is visible to the caller.):
// Simpler counter server.
type Counter int
func (ctr *Counter) ServeHTTP(c *http.Conn, req *http.Request) {
*ctr++;
fmt.Fprintf(c, "counter = %d\n", *ctr);
}
What if your program has some internal state that needs to be notified that a page has been visited? Tie a channel to the web page:
// A channel that sends a notification on each visit.
// (Probably want the channel to be buffered.)
type Chan chan *http.Request
func (ch Chan) ServeHTTP(c *http.Conn, req *http.Request) {
ch <- req;
fmt.Fprint(c, "notification sent");
}
Finally, let’s say we wanted to present on /args the arguments used when invoking the server binary. It’s easy to write a function to print the arguments:
func ArgServer() {
for i, s := range os.Args {
fmt.Println(s);
}
}
How do we turn that into an HTTP server? We could make ArgServer a method of some type whose value we ignore, but there’s a cleaner way. Since we can define a method for any type except pointers and interfaces, we can write a method for a function. The http package contains this code:
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(*Conn, *Request)
// ServeHTTP calls f(c, req).
func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) {
f(c, req);
}
HandlerFunc is a type with a method, ServeHTTP, so values of that type can serve HTTP requests. Look at the implementation of the method: the receiver is a function, f, and the method calls f. That may seem odd but it’s not that different from, say, the receiver being a channel and the method sending on the channel.
To make ArgServer into an HTTP server, we first modify it to have the right signature:
// Argument server.
func ArgServer(c *http.Conn, req *http.Request) {
for i, s := range os.Args {
fmt.Fprintln(c, s);
}
}
ArgServer now has same signature as HandlerFunc, so it can be converted to that type to access its methods, just as we converted Sequence to IntArray to access IntArray.Sort. The code to set it up is concise:
http.Handle("/args", http.HandlerFunc(ArgServer));
When someone visits the page /args, the handler installed at that page has value ArgServer and type HandlerFunc. The HTTP server will invoke the method ServeHTTP of that type, with ArgServer as the receiver, which will in turn call ArgServer (via the invocation f(c, req) inside HandlerFunc.ServeHTTP). The arguments will then be displayed.
In this section we have made an HTTP server from a struct, an integer, a channel, and a function, all because interfaces are just sets of methods, which can be defined for (almost) any type.
埋め込み¶
Goはよくみかける型駆動のサブクラス定義方法を提供しませんが、型をstructやinterfaceの中に埋め込むことによって実装の一部を”借りる”機能を持っています。
インターフェースの埋め込みは非常にシンプルです。io.Readerとio.Writerインターフェースについては前にふれました。これらの定義は次のとおりです。
type Reader interface {
Read(p []byte) (n int, err os.Error);
}
type Writer interface {
Write(p []byte) (n int, err os.Error);
}
ioパッケージはこのようなメソッドを実装したオブジェクトを定義するインターフェースを他にもいくつかエクスポートします。たとえば、ReadとWriteを含んだio.ReadWriterがあります。io.ReadWriterは明示的に2つのメソッドを並べることで実装することもできますが、次のように2つのインターフェースを埋め込んで新しいものを作る方が簡単かつ刺激的です。
// ReadWriteは基本的なReadとWriteメソッドをグループ化したインターフェース。
type ReadWriter interface {
Reader;
Writer;
}
これは見たとおりのことを行ないます。ReadWriterはReaderとWriterが提供するものを行なうことが可能です。つまり埋め込まれたインターフェースの和集合(これは互いに素な集合でなければなりません)です。インターフェースの中にはインターフェース以外のものは埋め込めません。
基本的な考え方はstructにもあてはまりますが、こちらはより広範囲に影響を及ぼします。bufioパッケージはbufio.Reader、bufio.Writerというふたつのstruct型を持ち、それらはもちろんioパッケージのものと類似したインターフェースの実装です。bufioはバッファリングされたreader/writerも実装しますが、これはreaderとwriterを1つのstructに埋め込むことによって行なわれます。型はstruct内に並べられますが、名前は与えられません。
// ReadWriterはReaderとWriterへのポインタを保持します。
// それがio.ReadWriterの実装となります。
type ReadWriter struct {
*Reader;
*Writer;
}
これは次のようにも書くことも可能です。
type ReadWriter struct {
reader *Reader;
writer *Writer;
}
しかし、こうしてしまうとフィールドのメソッドを使うため、そしてioインターフェースを満たすためには次のような転送メソッドを提供する必要があるでしょう。
func (rw *ReadWriter) Read(p []byte) (n int, err os.Error) {
return rw.reader.Read(p)
}
structを直接埋め込むことにより、このbookkeepingを避けられます。埋め込まれた型のメソッドはただで手に入ります。つまりbufio.ReadWriterはbufio.Readerとbufio.Writerのメソッドを持つだけではなく、io.Reader、io.Writer、io.ReadWriterのインターフェースを満たします。
埋め込みとサブクラスは重要な点で異なります。型が埋め込まれる時、その型のメソッドは外側の型のメソッドとなります。しかしそれらが呼び出される時、メソッドの受け取り側は内側の型であり、外側のものではありません。例では、bufio.ReadWriterのReadメソッドが呼び出される時、ちょうど前に書いた転送メソッドと同様の動作をします。つまり、受け側はReadWriterのreaderフィールドでありReadWriter自体ではありません。
埋め込みは簡単で便利なものです。次の例では埋め込みフィールドと通常の名前つきフィールドとが一緒になったものです:
type Job struct {
Command string;
*log.Logger;
}
Job型はLogとLogf、そしてlog.Loggerのメソッドを持ちます。もちろんLoggerにフィールド名をつけることも出来ますが、なくてもかまいません。これで次のようにログを書き出せます。
job.Log("starting now...");
Loggerはstruct内の普通のフィールドであり、いつもの方法で初期化できます。
func NewJob(command string, logger *log.Logger) *Job {
return &Job{command, logger}
}
埋め込まれたフィールドを直接参照する必要がある場合、そのフィールドの型名のパッケージ修飾を省いた形がフィールド名となります。Job変数、jobの*log.Loggerにアクセスする場合job.Loggerとなるでしょう。これはLoggerのメソッドを改良する際に便利です。
func (job *Job) Logf(format string, args ...) {
job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args));
}
埋め込み型名前の衝突問題を発生させますが、それらを解決するルールは簡単なものです。まず、フィールドまたはメソッドXは奥深くにネストされた型のXを隠してしまいます。もしlog.LoggerがCommandというフィールドやメソッドを含んでいたら、JobのCommandフィールドが優先されます。
次に、同じ階層に同じ名前が現われる場合は通常エラーとなります。Job structがLoggerというフィールドやメソッドを持つ時にlog.Loggerを埋め込むのは間違いでしょう。しかしその重複した名前が型定義の外で振れられない場合は問題とはなりません。これは外側で埋め込まれる型からの保護を提供します。そのフィールドが使用されない限りは名前が他の下位の型と衝突するものであっても問題はありません。
並列処理¶
通信による共有¶
並列プログラミングは大きなテーマなので、ここではGoに特化したハイライトだけを紹介します。
共有する変数に正しくアクセスする手段を提供するのに必要となる繊細さにより、多くの環境で並列プログラミングは難しいものとなっています。Goは異なるアプローチをとり、共有する値はチャンネル上でやりとりされ、実際スレッド間で活発に共有されることはありません。ある値へのアクセスはどんなときでもひとつのGoroutineしか持たず、設計上競合状態になることはありえません。このような考え方を奨励するため、次のスローガンにまとめました。
共有メモリを使って通信せず、通信によってメモリを共有せよ。
このアプローチは過剰だととることもできます。たとえばリファレンスカウントは整数の変数にmutexを使うことで最もうまく行なえるかもしれません。しかし、高レベルなアプローチではチャンネルを使ってアクセスを制御する方が明解で正しいプログラムをより簡単に書くことができます。
このモデルについて考えてみるため、1つのCPU上で走る典型的なシングルスレッドのプログラムについて考察してみましょう。これに同期プリミティブは必要ありません。もうひとつそのようなプログラムを走らせてみましょう。これもまた同期は不要です。ではこれらを互いに通信させてみましょう。その通信がsynchronizerであれば、まだ同期は不要です。たとえばUnixのパイプラインがこのモデルに完全にあてはまります。Goの並列処理のアプローチはHoareのCommunicating Sequential Processes (CSP)に由来するものですが、Unixパイプのtype-safe generalizationとしてみることもできるでしょう。
Goroutines¶
スレッド、コルーチン、プロセスなどの既存の用語は誤った意味合いを与えるので、goroutineと呼ばれます。goroutineは簡単なモデルで、他のgoroutineと同一のアドレススペース内で並列に実行される関数です。これは軽量でスタックスペースより少し多いほどのコストしかかかりません。スタックは小さく始まるためコストが小さく、必要に応じてヒープ領域を確保(または解放)することにより大きくなります。
goroutineは複数のOSスレッド上に多重化されているので、そのうちのひとつが入出力の待ち状態でブロックされているときでも他のものは実行し続けられます。これによりはスレッドの生成、管理に関する多くの複雑性は隠されます。
関数やメソッドの呼び出しの前にgoキーワードを置くと、呼び出しを新規のgoroutine内で実行することができます。処理が完了するとgoroutineは無言で終了します。(コマンドをバックグラウンドで起動するUnixシェルの&記法に似ています。)
go list.Sort(); // list.Sortを並列に実行し、完了を待たない。
関数リテラルはgoroutineの起動をする際に重宝するでしょう。
func Announce(message string, delay int64) {
go func() {
time.Sleep(delay);
fmt.Println(message);
}() // 括弧に注目。関数は呼び出される必要がある。
}
Goでは関数リテラルはクロージャなので、関数内で参照される変数はそれがアクティブな間存在することが保証されます。
以上の例は関数側からの処理の終了を通知する方法がないため、十分に実用的なものではありません。そのためにはチャンネルが必要となります。
チャンネル¶
map同様、チャンネルは参照型でありmakeによって領域が割り当てられます。整数パラメータが与えられている場合、それがチャンネルのバッファサイズとなります。デフォルトは0であり、その際はバッファされない、または同期チャンネルとなります。
ci := make(chan int); // バッファされない整数のチャンネル
cj := make(chan int, 0); // バッファされない整数のチャンネル
cs := make(chan *os.File, 100); // バッファされるファイルのポインタのチャンネル
チャンネルは通信(値のやりとり)と同期(2つの演算(goroutine)が機知の状態であることを保証すること)を同時に実現します。
チャンネルを使った便利なイディオムが多く存在します。ここで1つ紹介しましょう。前節ではソートをバックグラウンドで実行しました。チャンネルは開始したgoroutineにソートの完了を待たせることができます。
c := make(chan int); // チャンネルを確保
// ソートをgoroutine内で実行し、終了次第シグナル
go func() {
list.Sort();
c <- 1; // シグナルの発行。値は何でもOK。
}();
doSomethingForAWhile();
<-c; // ソートの完了を待つ。送られてくる値は破棄。
受け取り側は受け取るデータが来るまでブロックされます。チャンネルがバッファなしのものであれば、送信側は受け取り側が値を受け取るまでブロックされます。チャンネルがバッファされている場合、送信側がブロックされるのは値がバッファにコピーされるまでだけです。バッファが一杯になっている場合、受け取り側が値を取得するまでブロックされることになります。
バッファされたチャンネルは、例えばスループットを制御するなどのセマフォとして利用することができます。以下の例では、受信したリクエストはhandleに渡され、それが値をチャンネルに送り、リクエストを処理、そしてチャンネルからの値の受信を行います。チャンネルバッファの容量は同時に処理できる呼び出しを制限することになります。
var sem = make(chan int, MaxOutstanding)
func handle(r *Request) {
sem <- 1; // アクティブなキューがなくなるまで待つ。
process(r); // 長時間かかるかもしれない。
<-sem; // 終了。次に実行するリクエストを有効化する。
}
func Serve(queue chan *Request) {
for {
req := <-queue;
go handle(req); // handleの終了を待たない。
}
}
次のものは同様のことをリクエストチャンネルから読み込みを行う一定数のhandle goroutineを開始することによって実現しています。goroutineの数が同時呼び出し可能な数を制限することになります。Serve関数は終了指示を受けとるためのチャンネルも受け付けます。goroutineの起動後、そのチャンネルからの読み込みでブロックします。
func handle(queue chan *Request) {
for r := range queue {
process(r);
}
}
func Serve(clientRequests chan *clientRequests, quit chan bool) {
// ハンドラの開始
for i := 0; i < MaxOutstanding; i++ {
go handle(clientRequests)
}
<-quit; // 終了指示待ち。
}
チャンネルのチャンネル¶
Goの重要な性質のひとつとして、チャンネルは他のものと同じように割り当て、受け渡しが行なえるファーストクラス値であるということがあげられます。これは安全かつ並列な多重分離の実装でよく使用されます。
前節の例でhandle()は理想的なリクエストハンドラでしたが、それが何の型を処理するかを定義しませんでした。もしその型が返信に使用するチャンネルを含んでいれば、クライアントは答えを受け取る口を渡すことが出来ます。これの略図としてRequestの定義を次に示します。
type Request struct {
args []int;
f func([]int) int;
resultChan chan int;
}
クライアントはリクエストオブジェクト内に関数とその引数、そして答えを受け取るチャンネルを用意します。
func sum(a []int) (s int) {
for _, v := range a {
s += v
}
return
}
request := &Request{[]int{3, 4, 5}, sum, make(chan int)}
// リクエストを送信
clientRequests <- request;
// レスポンスを待つ
fmt.Printf("answer: %d\n", <-request.resultChan);
サーバーサイド側ではhandler()関数だけがそれを変更します。
func handle(queue chan *Request) {
for req := range queue {
req.resultChan <- req.f(req.args);
}
}
これを現実的なものとするためにやるべきことがあるのは明らかですが、このコードは速度制限付きの並列ノンブロッキングRPCシステムのフレームワークであり、ミューテックスは見当りません。
Parallelization¶
Another application of these ideas is to parallelize a calculation across multiple CPU cores. If the calculation can be broken into separate pieces, it can be parallelized, with a channel to signal when each piece completes.
Let’s say we have an expensive operation to perform on a vector of items, and that the value of the operation on each item is independent, as in this idealized example:
type Vector []float64
// Apply the operation to n elements of v starting at i.
func (v Vector) DoSome(i, n int, u Vector, c chan int) {
for ; i < n; i++ {
v[i] += u.Op(v[i])
}
c <- 1; // signal that this piece is done
}
We launch the pieces independently in a loop, one per CPU. They can complete in any order but it doesn’t matter; we just count the completion signals by draining the channel after launching all the goroutines:
const NCPU = 4 // number of CPU cores
func (v Vector) DoAll(u Vector) {
c := make(chan int, NCPU); // Buffering optional but sensible.
for i := 0; i < NCPU; i++ {
go v.DoSome(i*len(v)/NCPU, (i+1)*len(v)/NCPU, u, c);
}
// Drain the channel.
for i := 0; i < NCPU; i++ {
<-c // wait for one task to complete
}
// All done.
}
A leaky buffer¶
The tools of concurrent programming can even make non-concurrent ideas easier to express. Here’s an example abstracted from an RPC package. The client goroutine loops receiving data from some source, perhaps a network. To avoid allocating and freeing buffers, it keeps a free list, and uses a buffered channel to represent it. If the channel is empty, a new buffer gets allocated. Once the message buffer is ready, it’s sent to the server on serverChan:
var freeList = make(chan *Buffer, 100)
var serverChan = make(chan *Buffer)
func client() {
for {
b, ok := <-freeList; // grab a buffer if available
if !ok { // if not, allocate a new one
b = new(Buffer)
}
load(b); // read next message from the net
serverChan <- b; // send to server
}
}
The server loop receives messages from the client, processes them, and returns the buffer to the free list:
func server() {
for {
b := <-serverChan; // wait for work
process(b);
_ = freeList <- b; // reuse buffer if room
}
}
The client’s non-blocking receive from freeList obtains a buffer if one is available; otherwise the client allocates a fresh one. The server’s non-blocking send on freeList puts b back on the free list unless the list is full, in which case the buffer is dropped on the floor to be reclaimed by the garbage collector. (The assignment of the send operation to the blank identifier makes it non-blocking but ignores whether the operation succeeded.) This implementation builds a leaky bucket free list in just a few lines, relying on the buffered channel and the garbage collector for bookkeeping.
エラー¶
ライブラリルーチンはしばしば、呼び出し元へエラー情報の一種を返さなければなりません。先に述べたように、Goの複数の値を返す機能は、通常の戻り値の一緒に詳細なエラー内容を返すことを容易にします。エラーは以下に示す単純なインターフェースのos.Error型であることが規約によって決められています:
type Error interface {
String() string;
}
ライブラリの作者は、このインターフェースを実装し、より高機能なモデルを秘匿して作ることが自由にできます。このことによって、単純なエラー内容を見せるだけではなく、より詳細なエラーコンテキストを提供することを可能にします。例として、os.Open が返す os.PathError を示します:
// PathError は、エラー内容、エラー発生時の動作およびファイルパスを保持します。
type PathError struct {
Op string; // "open"、"unlink"など。
Path string; // エラー発生に関連するファイルパス。
Error Error; // システムコールによって返されるエラー内容。
}
func (e *PathError) String() string {
return e.Op + " " + e.Path + ": " + e.Error.String();
}
PathErrorが生成するエラーメッセージは次のようなものです:
open /etc/passwx: no such file or directory
問題のあるファイル名、動作、およびそれらによって引き起こされたオペレーティングシステムのエラーを含む、このようなエラーメッセージは、呼び出し元から相当離れている位置で発生したものであってもなお有用です。なぜなら、簡素な”no such file or directory”というエラーメッセージに比べて、非常に有益だからです。
呼び出し元で、正確で詳細なエラー内容を必要とする場合は、type switch文や型アサーションによって、特定のエラー時の詳細なエラー内容を得ることができます。PathErrorsの場合は、復旧可能なエラーであるかどうかを、内部のエラー内容フィールドによって調べることができる場合があります:
for try := 0; try < 2; try++ {
file, err = os.Open(filename, os.O_RDONLY, 0);
if err == nil {
return
}
if e, ok := err.(*os.PathError); ok && e.Error == os.ENOSPC {
deleteTempFiles(); // Recover some space.
continue
}
return
}
ウェブサーバ¶
最後に、Goの完全なプログラムとして、ウェブサーバを作ってみましょう。このプログラムは、実際にはウェブ中継サーバの一種です。Googleは、データを自動的に書式・体裁を整え、図表やグラフを作成するサービスを http://chart.apis.google.com にて提供しています。しかし、それは対話的に使用することは難しいです。なぜなら、クエリとしてURLにデータを埋め込む必要があるためです。これから作成するプログラムは、データの一形式を作成する優れたインターフェースを提供します。短いテキストが与えられると、プログラムはQRコード(与えられたテキストがエンコードされた粒子状の四角形)を生成するようにチャートサーバにリクエストをします。生成されたQRコードは携帯電話のカメラによって取り込むことができます。取り込まれたQRコードは、例えば(携帯電話のとても小さいキーでURLを打つのを省略するための)URLとして解読されます。
以下に示すのが、プログラム全体です。 ソースコードの後で、プログラムについて説明します:
package main
import (
"flag";
"http";
"io";
"log";
"strings";
"template";
)
var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
var fmap = template.FormatterMap{
"html": template.HTMLFormatter,
"url+html": UrlHtmlFormatter,
}
var templ = template.MustParse(templateStr, fmap)
func main() {
flag.Parse();
http.Handle("/", http.HandlerFunc(QR));
err := http.ListenAndServe(*addr, nil);
if err != nil {
log.Exit("ListenAndServe:", err);
}
}
func QR(c *http.Conn, req *http.Request) {
templ.Execute(req.FormValue("s"), c);
}
func UrlHtmlFormatter(w io.Writer, v interface{}, fmt string) {
template.HTMLEscape(w, strings.Bytes(http.URLEscape(v.(string))));
}
const templateStr = `
<html>
<head>
<title>QR Link Generator</title>
</head>
<body>
{.section @}
<img src="http://chart.apis.google.com/chart?chs=300x300&cht=qr&choe=UTF-8&chl={@|url+html}"
/>
<br>
{@|html}
<br>
<br>
{.end}
<form action="/" name=f method="GET"><input maxLength=1024 size=70
name=s value="" title="Text to QR Encode"><input type=submit
value="Show QR" name=qr>
</form>
</body>
</html>
`
上記のソースコードは、main関数まで、処理内容を容易に追うことができるはずです。flagはこのサーバのデフォルトHTTPポートを設定します。テンプレート変数のtemplでは、面白いことが起こります。このテンプレート変数は、ページを表示するためにサーバにより実行されるHTMLテンプレートを生成します。詳細は、すぐ後で説明します。
main関数はフラグ変数を処理してから、上述したメカニズムを用いて、このサーバのルート(/)パスにQR関数をバインドします。そして、このサーバを起動するためにhttp.ListenAndServe関数を呼びます。この関数は、サーバが起動している間は排他的に動作します。
QR関数がフォームデータを含むリクエストを受け取ると、フォームデータのsパラメータの値に基づいて、テンプレートを処理して出力します。
json-templateに影響を受けたtemplateパッケージは強力です。このプログラムは、単にtemplateパッケージの能力を、ほんの一部使用しているだけです。端的に説明すると、templ.Executeに渡されたデータ(この場合はフォームデータ)から得た要素に逐次代入することで、テキストを書き換えています。テンプレートテキスト(templateStr)においては、波括弧{}で囲まれた要素がテンプレートアクションであることを意味します。 {.section @} から {.end} の間の部分はデータ@とともに処理されます。@は『現在の要素』の簡便な記法です。現在の要素、とはフォームの値のことです。(もしフォームの値が空の場合には、この部分( {.section @} から {.end} の間)は出力されません。)
{@|url+html} という記述は、フォーマッタマップ(fmap)に”url+html”という名前でインストールされたフォーマッタを利用してデータを処理するように命令します。
テンプレートの残りのテキストは、ページが読み込まれる際に表示される単なるHTMLです。もし、これまでの説明が不十分だと感じるなら、templateパッケージのより徹底的な解説ドキュメントを参照してください。
そして、あなたはわずかな行数のコードに加えて、いくらかのデータ駆動のHTMLテキストを記述することで、便利なウェブサーバを手に入れることができました。Goはわずかな行数で、非常に多彩な機能を実現できる強力な言語なのです。
特に断りが無い限り、このドキュメント内容は、Creative Commons Attribution 3.0の元にライセンスされます。