このドキュメントは、http://golang.org/doc/go_tutorial.htmlの翻訳です。


はじめに
Hello, World
セミコロン
コンパイル
Echoコマンドの実装例
Goの型について
メモリの割り当てについて
定数について
I/O パッケージ
catコマンドの実装例
ソート
文字出力
素数
多重化

はじめに

このドキュメントはCまたはC++プログラマ向けに書かれたGo言語のチュートリアルです。Go言語の全般的なガイドではありません。現在のところ全般的なガイドに一番近い資料はGo言語仕様です。このチュートリアルを読み終えたら、次に実践Go言語を読んでください。こちらにはもっと詳しい言語の活用方法や、Go言語のプログラミングスタイルやイデオムについての記載があります。またGo言語の3日間学習コースのスライドも合わせてご覧ください。そこではGo言語のバックグラウンドや数多くのサンプルを紹介しています。(Day 1, Day 2, Day 3)

この資料では、一連のサンプルプログラムを通してGo言語の特色を説明します。サンプルプログラムは記述された単位でコンパイル・実行可能です。またこれらサンプルプログラムはリポジトリの/doc/progs/ディレクトリに格納されています。

Hello, World

おなじみの、Hello, Worldから始めてみましょう。

package main

import fmt "fmt" // 入出力フォーマットを実装したパッケージ

func main() {
    fmt.Printf("Hello, world; or Καλημέρα κόσμε; or こんにちは 世界\n")
}

packageステートメントを使ってこのGo言語ソースファイルがどのパッケージに属しているかを必ず宣言します。また別のパッケージに含まれているものを使用するためにはimportステートメントを使います。このプログラムではfmtパッケージをインポートし、fmt.Printfのようにパッケージ名を指定し、関数名の先頭を大文字にして呼び出しています。

関数を記述するときは関数名の前にfuncキーワードを書きます。プログラムのmainパッケージ内のmain関数が、まず最初(初期化処理後)に実行されます。

文字定数にはUTF-8エンコードのUnicodeを使うことができます。(Go言語のソースファイルはUTF-8エンコードと規定されています)

コメントの書き方はC++言語と同じです。

/* ... */
// ...

出力についてはもっと後で説明します。

セミコロン

すでに気づかれたかと思いますがプログラム内にはセミコロンがありません。Go言語のコードでは通常、forループの各節を区切るぐらいにしかセミコロンは使いません。各ステートメントの終端にセミコロンは不要です。

セミコロンは実際にはCやJavaといった開発言語と同じ扱いですが、ステートメントの終わりとみなせる全ての行末にセミコロンが自動的に挿入されるため、セミコロンを入力する必要はありません。

これがどのように行われるか詳細はGo言語仕様を見ていただきたいのですが、いま知っておかなくてはならないことは行末にセミコロンを書く必要がないことだけです。(一行に複数のステートメントを書きたいときはセミコロンを挿入することもできます。) 補足になりますが、波括弧{}直前のセミコロンも不要です。

この仕組みは、セミコロンのないすっきりしたコードを書くのに役立っています。ひとつ気をつけなければならない重要なこととして、ifのような本体部を持つステートメントでは、本体部の開始波括弧{ifと同じ行に書かなくてはならないことです。こうしないとコンパイルに失敗するか思いがけない結果が生じます。若干ですが言語の仕様によって波括弧の書き方が強制されています。

コンパイル

Go言語はコンパイル言語です。現在2種類のコンパイラがあります。GccgoはGCCをバックエンドとするGo言語コンパイラです。
もう一方のコンパイラ群はアーキテクチャごとに名前が付けられていて、6gは64ビットx86用、8gは32ビットx86用といった具合です。後者のほうはかなりコンパイル速度が速いのですが、吐き出されたコードはgccgoには劣ります。これを書いている今時点(2009年の終り)では後者のほうが実行できるシステムが多いのですが、gccgoもこれに追随しています。

下に示すのがコンパイルとプログラム実行の手順です。これは6gを使用した例です。

$ 6g helloworld.go  # コンパイル; 作成されるオブジェクトは helloworld.6
$ 6l helloworld.6   # リンク; 出力は 6.out
$ ./6.out
Hello, world; or Καλημέρα κόσμε; or こんにちは 世界
$

gccgoを使った場合のほうが従来のやり方に近いです。

$ gccgo helloworld.go
$ a.out
Hello, world; or Καλημέρα κόσμε; or こんにちは 世界
$

Echoコマンドの実装例

次は、Unixのecho(1)コマンドを実装した例です。
echoはコマンドラインパラメータから渡された文字列を標準出力に書き出すユーティリティです。

package main

import (
    "os"
    "flag" // コマンドラインオプションのパーサー
)

var omitNewline = flag.Bool("n", false, "don't print final newline")

const (
    Space   = " "
    Newline = "\n"
)

func main() {
    flag.Parse() // パラメータリストを調べてflagに設定
    var s string = ""
    for i := 0; i < flag.NArg(); i++ {
        if i > 0 {
            s += Space
        }
        s += flag.Arg(i)
    }
    if !*omitNewline {
        s += Newline
    }
    os.Stdout.WriteString(s)
}

短いプログラムですが、目新しいことがいくつか含まれています。前のhello worldプログラムで関数の前にfuncキーワードを使ったようにvarconsttype(これはまだ使っていません)キーワードもimport同様に宣言を開始するために使用します。このimportconstのように同じ種類の宣言を丸括弧()内の各行に記述してひとまとめにできることを覚えておいてください。ただし必ずこうすべきという訳ではありません。

const Space = " "
const Newline = "\n"

このechoプログラムでは*os.File型のStdout変数にアクセスするために"os"パッケージをインポートしています。このimportステートメントも実際は宣言のひとつです。前の”hello world”で記述したimportの使い方(import fmt "fmt")で説明すると、2つのfmtのうち最初のfmtはプログラムコードがパッケージメンバーにアクセスする際に使われる識別子です。後ろの"fmt"はパッケージをカレントディレクトリもしくは所定の場所から読み込むためのファイル名として使われます。

echoプログラムではimportのときに明示的に名前をつけませんでした。名前をつけないときはパッケージのファイル名がコードからアクセスするときに使用する名前となります。すなわち”hello world”プログラムのほうは、単にimport "fmt"とも書き表せます。

インポートの際、明示的に名前を付けるかどうかは自由です。ただし名前を付けなければならないケースはパッケージ名が競合したときだけです。

os.StdoutWriteStringメソッドは、文字列の出力に使用します。

flagパッケージをインポートしたあと、echoの-nフラグの値を格納するためのomitNewlineというグローバル変数をvar宣言を使用し作成と初期化を行なっています。この変数の型は*bool型(=bool型のポインタ)です。

main.main関数のflag.Parseのところでコマンドラインパラメータの解析(parse)を行い、出力する文字列を格納するためのローカル変数を次の行で宣言しています。

変数の宣言は下の形式です。

var s string = ""

var キーワードに続いて、変数名、変数の型、イコール記号、初期値を記述します。

Go言語はシンプルさを目指しており、この宣言文を短くすることができました。文字定数はstring型なので、わざわざそれをコンパイラに伝える必要はありません。次のように書き表せます。

var s = ""

また、さらに短い書き方が出来ます。

s := ""

この:= 演算子は初期値を宣言する際にGo言語で多用されます。 その次の行のfor文でも使われています。

for i := 0; i < flag.NArg(); i++ {

flagパッケージはコマンドラインパラメータを解析した際、フラグ以外のパラメータをリスト内に残したままにします。リストからは内部要素を繰り返して取り出す(イテレートする)ことが可能です。

Go言語のforステートメントはC言語と比べると若干違いがあります。まずループ処理を記述できるのはforだけで、whiledoはありません。次にfor節は丸括弧()で括る必要がありません。ただしループの本体部分は波括弧{}でくくる必要があります。ifswitchステートメントもこれと同様です。チュートリアルの後ろのほうでは、for文のこれと違う書き方がでてきます。

ループ本体では、+=記号を使って引数と区切りのスペースを文字列sに結合しています。ループを抜けた後は、-nフラグがセットされていなければ改行コードを文字列に加え、最後に作成した文字列を標準出力に書き出しています。

main.mainはパラメータ・戻り値を持たない関数なので、main.main関数の最後まで実行されれば処理が成功したことになります。エラーを返したいときは、下の呼び出しをします。

os.Exit(1)

このosパッケージには、ほかにもスライスのos.Args(flagパッケージがコマンドラインパラメータにアクセスするために使用しています)ど開始時に役立つものが含まれています。

Goの型について

Go言語も他の言語と同じようにintuint(符号なしint)型といったシステムによってサイズが異なる型を持ってます。他にもサイズを明示したint8float64型もあり、加えてuintuint32といった符号なし整数型もあります。これらはすべて異なる型であり、たとえint型が32ビットのシステム上であっても、intint32は同じ型ではありません。一方でbyte型はuint8型の同義語であり、string型の要素です。

浮動小数点型は、常にサイズを有します。float32float64、それに加え、複素数のcomplex64(2つのfloat32で構成)、complex128(2つのfloat64で構成)があります。なお、複素数はこのチュートリアルでは取り扱いません。

string型も同様に言語に組み込まれている型のひとつで、これについて説明します。string型は単なるbyte型の配列ではなく不変であり、一度string型の値を作成すると値は変更できません。あたりまえですが代入によりstring型の値を入れ替えることは可能です。strings.goプログラムから抜き出した下のコードは正しく動作します。

s := "hello"
if s[1] != 'e' {
    os.Exit(1)
}
s = "good bye"
var p *string = &s
*p = "ciao"

しかし下のコードはstring型の値を変更しようとしているので不正なコードです。

s[0] = 'x'
(*p)[1] = 'y'

Go言語のstring型はC++言語の文字列定数(const string)のようなもので、string型のポインタはconst stringの参照とほぼ同じです。

はい、ここでポインタという言葉が出てきました。Go言語ではポインタの扱いが若干シンプルとなっています。では続きをどうぞ。

配列はこのように宣言します。

var arrayOfInt [10]int

配列はstring型と似ていますが、要素の値は変更可能です。C言語では上の例のarrayOfInt変数をint型のポインタとしても扱えますがGo言語では扱えません。Go言語の配列は値であり、配列のポインタはとても重要(かつ有益)であるため追って説明します。

配列のサイズ情報は、その型の一部分ですが、どのような配列・サイズであっても要素型が同じであれば、それを参照するスライス変数を宣言できます。スライス式は、a[low : heigh]形式で、これは内部配列のインデックスlowからhigh-1までの範囲を表します。この式から得られるスライスは、0からhigh-low-1までのインデックスを持ちます。要は、スライスは配列と似ていますが、サイズを明示的に持たず([][10]の違い)、作成元である通常の配列(匿名であることが多い)を部分的に参照しています。配列だけではデータを共有はできませんが、複数のスライスが同一の配列を参照することでデータ共有が可能になります。

スライスは通常の配列より柔軟で、実体への参照であり、かつ効果的であるためGo言語のプログラム内で多用されます。その一方、スライスでは通常の配列のようには細かいメモリ割り当ての制御はできないため、たとえば100要素の配列を構造体内に持たせるようなときは通常の配列を使ってください。スライスを作成するには、次のように複合値コンストラクタ(この式は、型の後ろに波括弧で囲った式を伴う)を使用します。

[3]int{1,2,3}

この場合、コンストラクタは、3つのintを持つ配列を造ります。

関数に配列を受け渡すときは、大抵はパラメータをスライスとして宣言します。関数を呼び出すとき、効率のため配列をスライスし、スライス参照を作成し、それを渡すようにしてください。スライスの下限、上限値は、デフォルトでは、既存オブジェクトの下限、上限値と同じであるため、簡潔に[:]とだけ書けば、配列全体をスライスします。

下は、スライスを使用した関数の例です。(sum.goから)

func sum(a []int) int { // intを返す
    s := 0
    for i := 0; i < len(a); i++ {
        s += a[i]
    }
    return s
}

sum関数の戻り値の型はintです。戻り値の型は関数のパラメータリストの直後に記述します。

下は、関数を呼び出すため、配列をスライスしています。この複雑な呼び出し(下にもっと簡単な方法を示します)は、配列を造り、それをスライスしています。

s := sum([3]int{1,2,3}[:])

配列を作成するときに要素数を指定せずコンパイラ側に数えてもらうには、配列のサイズに...を指定します。

s := sum([...]int{1,2,3}[:])

これは無駄に長い書き方です。実際のところ、データ構造内のメモリ割り当てについて気にとめなければ、サイズを指定せず、空の角括弧のスライスで充分です。

s := sum([]int{1,2,3})

マップも同様に、次のように初期化できます。

m := map[string]int{"one":1 , "two":2}

sum関数内で初登場した、組み込み関数lenは要素数を返します。このlen関数は文字列、配列、スライス、マップ、チャネルで利用可能です。

話は変わりますが、同様に文字列、配列、スライス、マップ、チャネルに対して機能するrangeについて説明します。これはforループ節で使用します。説明するより見てもらったほうが早いと思います。

for i := 0; i < len(a); i++ { ... }

これはスライス(またはマップなど)の要素数ループを繰り返します。これを下のように書き換えることが出来ます。

for i, v := range a { ... }

この例ではi変数にループのインデックス、v変数にrangeで指定したaの要素が順に代入されます。rangeの使用例は 実践Go言語 をご覧ください。

メモリの割り当てについて

Go言語のほとんどの型は値で、int型、構造体、配列へ代入することはオブジェクトの内容をコピーすることです。新しく変数を割り当てるには組み込み関数newを使用します。このnewは割り当てたメモリへのポインタを返します。

type T struct { a, b int }
var t *T = new(T)

より慣用的な書き方をすると

t := new(T)

一方マップ、スライス、チャネル(後で説明)といった型は、他のオブジェクトへの参照です。このスライスやマップの内容を変更すると、その参照先のデータをおなじく参照している別の変数にも影響を及ぼします。この3つの型を使うときは、組み込み関数のmakeを使います。

m := make(map[string]int)

上のステートメントは新しいマップを初期化して要素が格納できる状態にします。マップの宣言だけを行うには次のようにします。

var m map[string]int

これは何も要素を持てないnil参照を作成します。マップを使用するには最初にmakeを使用して参照を初期化するか、またはすでに作成済みのマップを代入しなければなりません。

new(T)*Tを返しますが、make(T)Tを返すので注意してください。参照オブジェクトの割り当てに誤ってmakeでなくnewを使ってしまうと、nil参照へのポインタが返ります。これは初期化していない変数を作り、そのアドレスを取得したことと同じです。

定数について

Go言語の整数(int)型には多くのサイズが用意されていますが、整数の定数は一種類だけです。0LL0x0ULといった定数はありません。その代わりに整数の定数は大きな桁数を持っているとして扱われ、その値より小さい精度の変数へ代入しようとしたときだけオーバーフローを起こします。

const hardEight = (1 << 100) >> 97  // これは正しいです

Go言語仕様を参照してもらうのが一番ですが、ここにも実例が少しあります。

var a uint64 = 0  // aはuint64型で値は0
a := uint64(0)    // 上と等価、変換(conversion)を使用
i := 0x1234       // iはデフォルトのint型
var j int = 1e6   // 正しい(1000000 はintで表現可能)
x := 1.5          // float64型。これは浮動小数点型定数のデフォルトの型
i3div2 := 3/2     // 整数の割り算。値は1
f3div2 := 3./2.   // 浮動小数点の割り算。値は1.5

変換が行えるのは、符号やサイズの変換と、整数と浮動小数点間の変換のような単純なケースだけです。他にもいくつかケースがありますが、このチュートリアルの範囲外といたします。定数を変数に代入するときに具体的なサイズと型を持つようになることを除いて、数値の自動変換はGo言語では一切行われません。

I/O パッケージ

次は、open, close, read, writeといったI/O関数を使った簡単なパッケージを見てみましょう。ここからfile.goファイルが始まります。

package file

import (
    "os"
    "syscall"
)

type File struct {
    fd   int    // ファイルデスクプリタ番号
    name string // Open時のファイル名
}

最初の数行は、fileパッケージの宣言と2つのパッケージのインポートです。osパッケージはオペレーションシステム間の様々な差を吸収し、ファイルへの一貫したアクセスを提供します。そのosパッケージのエラーハンドリング機能を使ってファイルI/Oの基本を実装してみましょう。

もうひとつのsyscallパッケージは、オペレーティングシステムを呼び出す低レベルなシステムコールインタフェースを提供します。

次の行では新しく型を定義しています。型の定義はtypeキーワードで始まり、このケースではFileという名前の構造体(struct)を定義しています。少し趣向を凝らし、このFile構造体内にファイルデスクリプタ(fd)と、それの参照先ファイルのファイル名(name)のフィールドを定義しました。

Fileという型の名前が大文字から始まるため、この型はパッケージの外部(パッケージの利用者)から利用することができます。Go言語の情報の可視性についてのルールは単純です。名前(トップレベルの型名、関数名、メソッド名、定数名、変数名、構造体のフィールドおよびメソッド名)の先頭一文字が大文字になっていれば、パッケージの利用者側から参照可能となります。すなわち大文字にしなければ、それが定義されているパッケージ内からしか参照できません。このルールはコンパイラによって実施されるため、絶対的なルールとなっています。この外部パッケージから可視状態であることをGo言語の用語で「エクスポートされた(exported)」と言います。

このFile型の場合、フィールドがすべて小文字なのでパッケージの使用者側からは不可視ですが、後ほど大文字で始まるエクスポートされたメソッドをいくつか追加していきます。

最初はFile型オブジェクトを作成するファクトリ関数です。

func newFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    return &File{fd, name}
}

この関数の戻り値は新しく作られたFile構造体のポインタで内部フィールドにファイルデスクリプタとファイル名が格納されます。このコードはマップや配列を作成するときに用いられる書き方に似てますが、これはGo言語の”複合リテラル(composite literal)”という書き方で、これを使ってオブジェクトに新しいヒープ領域を割り当てています。次のような書き方もできます。

n := new(File)
n.fd = fd
n.name = name
return n

File型のような単純な構造体であれば、newFilereturnステートメントのように複合リテラルのアドレスを返すほうが簡単です。

このファクトリ関数を使用して、エクスポートされた*File型の変数を作成します。この変数名はおなじみですね。

var (
    Stdin  = newFile(syscall.Stdin, "/dev/stdin")
    Stdout = newFile(syscall.Stdout, "/dev/stdout")
    Stderr = newFile(syscall.Stderr, "/dev/stderr")
)

newFileは内部関数なのでエクスポートされません。エクスポートすべきは次のOpenFileファクトリ関数です。(この名前についてはあとで説明します)

func OpenFile(name string, mode int, perm uint32) (file *File, err os.Error) {
    r, e := syscall.Open(name, mode, perm)
    if e != 0 {
        err = os.Errno(e)
    }
    return newFile(r, name), err
}

このたった数行内に、いくつかの新しいことが現れました。最初に目に付くのは、OpenFile関数がfileerr(エラーについてはあとで説明します)という複数の戻り値をもつことです。複数の戻り値は、丸括弧()で括ったリストを使って宣言します。構文的には2番目の引数リストがあるかのように見えます。先頭行のsyscall.Open関数もまた複数の戻り値を返すので、変数を複数宣言し戻り値を受け取っています。戻り値を受け取る変数reはともにint型です。(syscallパッケージを参照) 最後にOpenFileは、Fileのポインタとエラーの2つの値を返しています。syscall.Openがエラーとなったときは、ファイルデスクリプタrの値はマイナス値となり、newFile関数はnilを返します。

エラーについて説明します。osパッケージライブラリは包括的なエラーの概念を含んでいます。この例のように独自のインタフェースにも共通のエラーハンドリング方法を取り入れるのはコードの一貫性という意味でよい方法です。Openメソッドでは変換を利用してUnixのerrno値を整数型のos.Errnoに変換し、os.Error型を得ています。

関数名をOpenでなくOpenFileとしたのは、Goのosパッケージを真似るためで、この学習ではosパッケージを模倣します。osパッケージで最もよく利用されるのは、「読み込みのためのオープン」と「書き込みのためのクリエイト」、すなわちOpenCreateの2つです。OpenFileは、UnixシステムコールのOpenのように汎用的な位置づけとします。では、独自にOpenCreateを実装しましょう。これらは、ごく簡単なラッパーであり、本来は複雑な引数(特にCreate)を隠蔽し、指定誤りによるエラーを防ぎます。

const (
    O_RDONLY = syscall.O_RDONLY
    O_RDWR   = syscall.O_RDWR
    O_CREATE = syscall.O_CREAT
    O_TRUNC  = syscall.O_TRUNC
)

func Open(name string) (file *File, err os.Error) {
    return OpenFile(name, O_RDONLY, 0)
}
func Create(name string) (file *File, err os.Error) {
    return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

話を戻しましょう。File型オブジェクトが作成できるようになったので、つづいてメソッドを追加していきます。型にメソッドを宣言するには、定義する関数名の直前の括弧内に、レシーバを型を明示して記述します。下に記述する*Fileの各メソッドではそれぞれfileレシーバ変数を宣言しています。

func (file *File) Close() os.Error {
    if file == nil {
        return os.EINVAL
    }
    e := syscall.Close(file.fd)
    file.fd = -1 // so it can't be closed again
    if e != 0 {
        return os.Errno(e)
    }
    return nil
}

func (file *File) Read(b []byte) (ret int, err os.Error) {
    if file == nil {
        return -1, os.EINVAL
    }
    r, e := syscall.Read(file.fd, b)
    if e != 0 {
        err = os.Errno(e)
    }
    return int(r), err
}

func (file *File) Write(b []byte) (ret int, err os.Error) {
    if file == nil {
        return -1, os.EINVAL
    }
    r, e := syscall.Write(file.fd, b)
    if e != 0 {
        err = os.Errno(e)
    }
    return int(r), err
}

func (file *File) String() string {
    return file.name
}

暗黙的なthisを持たないため、構造体のメンバにアクセスするためにはレシーバ変数を使用しなければなりません。メソッドは構造体内で宣言するのではありません。構造体の宣言内で定義するのはデータメンバだけです。実はメソッドは構造体にだけではなく整数または配列などほぼすべての型に作成することができます。配列を使った例は後で記述します。

Stringメソッドは文字出力でよく使用されるメソッドで、後ほど解説します。

これらのメソッドはUnixのエラーコードEINVAL(これをos.Errorに変換した値)を返すためパブリック変数os.EINVALを使用しています。osパッケージライブラリにはこのような標準的なエラー値が定義されています。

今回は新しいパッケージを使います。

package main

import (
    "./file"
    "fmt"
    "os"
)

func main() {
    hello := []byte("hello, world\n")
    file.Stdout.Write(hello)
    f, err := file.Open("/does/not/exist")
    if f == nil {
        fmt.Printf("can't open file; err=%s\n", err.String())
        os.Exit(1)
    }
}

インポートの「./file」の「./」の部分は、コンパイラに対しパッケージがインストールされているディレクトリからインポートするのではなく、このmainパッケージ自身のディレクトリからインポートするように指示しています。(パッケージをインポートする前に、「file.go」がコンパイルされていなければなりません。)

これで、コンパイルと実行が可能になりました。Unixでは、次のような結果になります。

$ 6g file.go                       # fileパッケージのコンパイル
$ 6g helloworld3.go                # mainパッケージのコンパイル
$ 6l -o helloworld3 helloworld3.6  # リンク - "file"の指定は不要
$ helloworld3
hello, world
can't open file; err=No such file or directory
$

catコマンドの実装例

前回作成したfileパッケージを基にして、Unixユーティリティのcat(1)の簡易バージョンを作成します。このファイルはprogs/cat.goにあります。

package main

import (
    "./file"
    "flag"
    "fmt"
    "os"
)

func cat(f *file.File) {
    const NBUF = 512
    var buf [NBUF]byte
    for {
        switch nr, er := f.Read(buf[:]); true {
        case nr < 0:
            fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", f.String(), er.String())
            os.Exit(1)
        case nr == 0: // EOF
            return
        case nr > 0:
            if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr {
                fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", f.String(), ew.String())
                os.Exit(1)
            }
        }
    }
}

func main() {
    flag.Parse() // パラメータリストを調べてflagに設定
    if flag.NArg() == 0 {
        cat(file.Stdin)
    }
    for i := 0; i < flag.NArg(); i++ {
        f, err := file.Open(flag.Arg(i))
        if f == nil {
            fmt.Fprintf(os.Stderr, "cat: can't open %s: error %s\n", flag.Arg(i), err)
            os.Exit(1)
        }
        cat(f)
        f.Close()
    }
}

すでにこのコードを理解できるようになっていると思いますが、switchステートメントの新機能について説明しましょう。forループと同じく、ifswitchには初期化ステートメントを含めることができます。cat内のswitchステートメントでは、初期化ステートメントを使い、f.Readの戻り値を保持するためnrer変数を作成しています。(何行か下にあるifも同様です) swtichでは通常、値と一致するまでcaseを先頭からひとつずつ順に評価していきますが、caseに記述する式はswitchで指定した値と同じ型でありさえすれば定数や数値である必要はありません。

このswitchの値にはtrueが指定されています。このように値がtrueのときだけは値の記述は省略可能です。(forステートメントと同じです) 値が記述されていないときはtrueとして扱われます。このようなswitchステートメントは、if - elseステートメントを連ねたのと同じです。またswtichステートメントのcaseには暗黙的なbreakがあることをここに明記しておきます。

file.Stdout.Writeへの引数は、配列bufをスライスしています。Go言語でI/Oバッファを取り扱うときは通常、このようにスライスを使用します。

これから入力をrot13変換するオプション機能をもったcatの亜種を作成してみましょう。rot13とはアルファベットを一文字毎に13文字後のアルファベットに置き換えてつくる簡単な暗号です。(参考文献:Wikipedia) これはバイト処理で行えば簡単ですが、すこし回り道してGo言語のインタフェース(interface)を理解していきましょう。

catサブルーチンでは、f内のReadStringの2つのメンバしか使っていません。まずはそれらの2つのメソッドとまったく同じメソッド持つインタフェースを定義することから始めましょう。このコードは、progs/cat_rot13.goファイルにあります。

type reader interface {
    Read(b []byte) (ret int, err os.Error)
    String() string
}

readerインタフェースのこの2つのメソッドを持つ型であれば(たとえ他のメソッドを有していても)このインタフェースを実装していることになります。すなわちfile.Fileはこの2つのメソッドをすでに実装しているので、readerインタフェースを実装していることになります。catサブルーチンを少し変更し、引数を*file.Fileの代わりにreaderを受け取るようにするだけでもちゃんと動作しますが、せっかくなのでreaderを実装する別の型を記述してみましょう。新しく作成する型では別のreaderをラップし、そのreaderから取り出したデータにrot13変換を行います。型を定義しreaderインタフェースの2つのメソッドだけ記述すると、readerインタフェースの2個目の実装ができあがります。

type rotate13 struct {
    source reader
}

func newRotate13(source reader) *rotate13 {
    return &rotate13{source}
}

func (r13 *rotate13) Read(b []byte) (ret int, err os.Error) {
    r, e := r13.source.Read(b)
    for i := 0; i < r; i++ {
        b[i] = rot13(b[i])
    }
    return r, e
}

func (r13 *rotate13) String() string {
    return r13.source.String()
}
// rotate13の実装はここまで

(Read内で呼び出しているrot13関数は、取るに足らないので省略しています)

新しく追加した機能を利用するためにflagを定義します。

var rot13Flag = flag.Bool("rot13", false, "rot13 the input")

さらに、これを使うためにcat関数を少しだけ変更します。

func cat(r reader) {
    const NBUF = 512
    var buf [NBUF]byte

    if *rot13Flag {
        r = newRotate13(r)
    }
    for {
        switch nr, er := r.Read(buf[:]); {
        case nr < 0:
            fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", r.String(), er.String())
            os.Exit(1)
        case nr == 0: // EOF
            return
        case nr > 0:
            nw, ew := file.Stdout.Write(buf[0:nr])
            if nw != nr {
                fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", r.String(), ew.String())
                os.Exit(1)
            }
        }
    }
}

catの先頭のifで処理の準備を行っており、rot13フラグがtrueのときは引数で受け取ったreaderrotate13内にラップさせ入れ替えています。このラッピングさせる処理をmain関数内で行えば、cat関数は引数の型以外は変更しなくてもよかったのですがこれも学習と思ってください。ここで気をつける点は、インタフェース変数はポインタではなく値であることです。内部的に構造体のポインタを持っていますが、受け取る引数の型はreaderであり*readerではありません。

では、実行してみましょう。

$ echo abcdefghijklmnopqrstuvwxyz | ./cat
abcdefghijklmnopqrstuvwxyz
$ echo abcdefghijklmnopqrstuvwxyz | ./cat --rot13
nopqrstuvwxyzabcdefghijklm
$

このようにインタフェースを使ってファイルデスクリプタの実装が入れ替えられるので、依存性注入(dependency injection)も簡単に行えます。

このインタフェースは他の言語にはないGo言語の特徴です。インタフェース内に定義されている全てのメソッドを実装した型は自動的にそのインタフェースを実装したことになります。すなわち型は複数の異なるインタフェースを実装可能ということです。Go言語の型には階層はありませんが、その代わりインタフェースを使用することでrot13で行ったようにプログラムのアドホックな変更が容易に行えます。file.File型はreaderインタフェースを実装していますが、writerインタフェース、その他のインタフェースを必要に応じて実装可能です。

空インタフェースについて考えてみましょう。

type Empty interface {}

すべての型は空インタフェース(interface {})を実装しています。そのため、空インタフェースはコンテナの類を作るときに役立ちます。

ソート

Go言語のインタフェースはポリモーフィズムを提供します。ポリモーフィズムにより、オブジェクトのふるまいと実際の処理を完全に分離できるので、同一インタフェースを実装していても型が異なるオブジェクトを、ひとつの変数を使って(代入しなおす必要はありますが)アクセスすることができます。

下の簡単なソートアルゴリズムの例をみてください。このファイルはprogs/sort.goにあります。

func Sort(data Interface) {
    for i := 1; i < data.Len(); i++ {
        for j := i; j > 0 && data.Less(j, j-1); j-- {
            data.Swap(j, j-1)
        }
    }
}

SortのInterfaceに必要なのは3つのメソッドだけです。

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

Sort関数には、このLenLessSwapメソッドをさえ実装していればどんな型でも渡すことができます。sortパッケージには、整数値や文字列の配列をソートするのに必要なメソッドが用意されています。下は、int型の配列をソートするコードです。

type IntSlice []int

func (p IntSlice) Len() int           { return len(p) }
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p IntSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

構造体ではない型に対してメソッドを定義していることがお分かりいただけるでしょうか。どんな型であっても、それが定義されたパッケージ内であればメソッドを定義することができます。

これをテストするためのルーチンです。これはprogs/sortmain.goファイルにあります。ここではコードを省略するためsortパッケージのIsSortedメソッドを使って実際にソートされているか確認しています。

func ints() {
    data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
    a := sort.IntSlice(data)
    sort.Sort(a)
    if !sort.IsSorted(a) {
        panic("fail")
    }
}

新しい型をソート可能にするには、このようにその型に3つのメソッドを実装するだけです。

type day struct {
    num       int
    shortName string
    longName  string
}

type dayArray struct {
    data []*day
}

func (p *dayArray) Len() int           { return len(p.data) }
func (p *dayArray) Less(i, j int) bool { return p.data[i].num < p.data[j].num }
func (p *dayArray) Swap(i, j int)      { p.data[i], p.data[j] = p.data[j], p.data[i] }

文字出力

これまでは文字列のフォーマット出力は、あまり使ってきませんでした。このセクションではGo言語の文字列フォーマットI/Oがどのように動いているか解説します。

いままでのサンプルコードの中では簡単な使い方しかしていなかったfmtパッケージですが、このパッケージにはPrintf, Fprintfなどの関数が実装されています。fmtパッケージ内ではPrintfが次のシグネチャーで宣言されています。

Printf(format string, v ...interface{}) (n int, errno os.Error)

トークン...は、可変長変数リストの指定に使います。C言語では、stdarg.hマクロを使って扱います。Go言語では、可変引数関数は、指定した型の引数のスライスとして関数に渡されます。Printfでは、...interface{}と宣言されているので、受け取る型は、空インタフェースの値のスライス([]interface{})です。Printfでは、スライスをイテレートすることで、引数を調べることができ、各要素に対し、型スイッチやリフレクションライブラリを使って、値を解釈します。話がそれますが、このような実行時の解析処理は、Go言語のPrintfの優位性を説明するのに一役買っています。Printfには、引数の型を動的に調査する能力があるためです。

たとえば、C言語ではフォーマットと受け取ったパラメータとの型が一致している必要がありますが、Go言語ではほとんどの場合もっと簡単です。フォーマット%lludの代わりに%dとだけ記述すれば、Printfは整数のサイズ・符号を判断して一番適したフォーマットで出力します。

下は簡単な例です。

    var u64 uint64 = 1<<64 - 1
    fmt.Printf("%d %d\n", u64, int64(u64))

その出力です。

18446744073709551615 -1

もしフォーマットについて考えるのが面倒なら%vを使ってください。どんな値(配列や構造体でさえ)であってもそれに最適な書式で出力します。次はその例です。

    type T struct {
        a int
        b string
    }
    t := T{77, "Sunset Strip"}
    a := []int{1, 2, 3, 4}
    fmt.Printf("%v %v %v\n", u64, t, a)

その出力です。

18446744073709551615 {77 Sunset Strip} [1 2 3 4]

もしPrintfの代わりにPrintまたはPrintlnを使用するのであれば、フォーマットの指定は全く不要です。これらのルーチンは完全自動でフォーマットを行います。Print関数はパラメータで指定された値を%vフォーマットを使ったときと同じように出力します。一方Println関数はパラメータの間にスペースを、そして最後に改行コードを挿入します。下の2行の出力内容は、前回のPrintfの呼び出しと同じ結果になります。

    fmt.Print(u64, " ", t, " ", a, "\n")
    fmt.Println(u64, t, a)

自分で作った型をPrintfまたはPrintでフォーマットしたいときは、文字列を返すStringメソッドを実装してください。printルーチンは、出力しようとする値がStringメソッドを実装しているか調べ、実装していればそれを使います。ここに分かりやすい例があります。

type testType struct {
    a int
    b string
}

func (t *testType) String() string {
    return fmt.Sprint(t.a) + " " + t.b
}

func main() {
    t := &testType{77, "Sunset Strip"}
    fmt.Println(t)
}

*testTypeStringメソッドを持っているので、この型のデフォルトのフォーマット処理としてStringメソッドが出力時に使われます。

77 Sunset Strip

このStringメソッド内でフォーマットのためにSprint関数を呼び出しているように、自作のフォーマット処理から他のフォーマット処理を呼び出すこともできます。

Printfのもう一つの特徴として、フォーマット%Tは値そのものではなく型情報を文字列に変換します。これはポリモーフィックなコードをデバッグするときに役立ちます。

フラグや精度情報などを持った完全自作のフォーマット処理を記述することは可能ですが、このチュートリアルの範囲からはずれているためここでは説明しません。

ある型がStringメソッドを実装しているかどうかをPrintfがどのように判断しているかというと、値が『Stringメソッドを実装したインタフェース』に変換可能かを調べることで判定しています。

仮に変数vがあったとすると、次のように判断します。

type Stringer interface {
    String() string
}
s, ok := v.(Stringer)  // Test whether v implements "String()"
if ok {
    result = s.String()
} else {
    result = defaultOutput(v)
}

このコードでは、型アサーション(type assertion)を使用し、v変数の値がStringerインタフェースを実装しているか調べています。v.(Stringer)の箇所が型アサーションです。型アサーションに成功すると、指定したインタフェース型に変換されたインスタンスがs変数に設定され、ok変数にはtrueが設定されます。その後、変換されたs変数を使用してメソッドにアクセスします。(「カンマ+ok」という書き方は、型変換、マップ更新、通信処理において、処理が成功したかどうかを確認するときによく使われるGo言語のイディオムですが、チュートリアル内で使用しているのはここだけです) 値がインタフェースを実装していないときは、ok変数にはfalseが設定されます。

このStringerのコードのように、インタフェースに名前をつける際、メソッド名にer( またはr)をつけるというのがGo言語の習わしです。

最後の話題です。Printf系、Sprintf系がでてきましたが、当然Fprintf系もあります。C言語と異なりFprintfの第1パラメータはファイルではなく、ioパッケージで定義されているio.Writerインタフェースです。

type Writer interface {
    Write(p []byte) (n int, err os.Error)
}

このインタフェースは前出の習わしに従った名前です。WriteメソッドからWriterインタフェースと名づけられています。io.Readerio.ReadWriterの名前も同じルールで名づけられています。このように、ファイルだけでなく、ネットワークチャネル、バッファなど標準Writeメソッドを実装していればどんな型を使用してもFprintfを呼び出すとができます。

素数

やっと、プロセスと平行通信プログラミングのところまで到達しました。これは重要なセクションであり、このトピックにはある程度の知識が必要です。

「素数の篩(ふるい)」と呼ばれる古くから存在する素数を取得するプログラムがあります。(エラトステネスの篩はここで扱うアルゴリズムより効率的ではありますが、このセクションでは並列性を重要視しています) このアルゴリズムでは、まず2以上の自然数を順番に並べて数列を作成します。そこから素数をひとつづ取り出し、その素数の倍数すべてを数列から除外することを繰り返します。残った数列の先頭から取り出した数が次の素数であり、そこからまた倍数をすべて除外して次の数列を作成します。

下が、これのフロー図です。それぞれの箱は数列の先頭から取り出した素数であり、この箱で数列をフィルタリングして次の数列を作成しています。  sieve

整数の数列を作成するためにチャネルと呼ばれるGo言語の機能を使います。チャネルの基となったものはCSPであり、チャネルとは2つの並列動作している処理をつなぐ通信チャネルです。Go言語のチャネル変数は”通信を調整するランタイムオブジェクト”への参照です。新しいチャネルをつくるには、マップやスライスと同様にmakeを使用してください。

下はprogs/sieve.go内の関数です。

// チャネル'ch'に、2, 3, 4, ... という連番を送信
func generate(ch chan int) {
    for i := 2; ; i++ {
        ch <- i // 'i'をチャネル'ch'に送信
    }
}

このgenerate関数は、2,3,4,5,…という一連の値をパラメータchに送信します。送信には二項演算子<-を使用します。チャネルの送信ではch変数を受信する側がいなければブロックされ、誰かが受信状態になるまで送信を保留します。

次のfilter関数では3つのパラメータ、入力チャネル・出力チャネル・素数を受け取り、入力チャネルの値を出力チャネルにコピーします。そのとき素数で割り切れた値は除外します。受信に使用する単項演算子<-は、チャネル上から次の値を取り出します。

// チャネル'in'からチャネル'out'に値をコピー
// そのとき'prime'で割り切れる値を取り除く
func filter(in, out chan int, prime int) {
    for {
        i := <-in // 新しい変数'i'に'in'から値を受信する
        if i%prime != 0 {
            out <- i // 'i'をチャネル'out'に送信する
        }
    }
}

generator関数とfilters関数は平行動作します。Go言語は独自のプロセス/スレッド/軽量プロセス/コルーチンモデルを持っていますが、表記上の混乱を避けるためGo言語の平行実行処理をゴルーチン(goroutines)と呼ぶことにしています。ゴルーチンを開始するためには、キーワードgoを前に付けて関数を呼び出します。こうすると呼び出した関数はカレントの処理と同じアドレス空間のまま平行動作を開始します。

go sum(hugeArray) // sumの計算をバックグラウンドで行う

計算が終了したことを検知したいときは、つぎのようにして処理終了を通知するチャネルを関数に渡してください。

ch := make(chan int)
go sum(hugeArray, ch)
// ... しばらくの間、何かを行う
result := <-ch  // 結果を受信するまで待つ

素数の篩に話を戻しましょう。下のようにして篩パイプラインを組み合わせます。

func main() {
    ch := make(chan int)       // 新しいチャネルの作成
    go generate(ch)            // generate()をゴルーチンとして開始
    for i := 0; i < 100; i++ { // 最初の100個の素数を表示
        prime := <-ch
        fmt.Println(prime)
        ch1 := make(chan int)
        go filter(ch, ch1, prime)
        ch = ch1
    }
}

mainの先頭行でgenerate関数に渡すチャネルを作成、初期化しています。チャネルから素数をひとつ取り出し、新たにfilterをパイプラインに加えて、そこからの出力でch変数を置き換えます。

この篩プログラムは、共通プログラミングパターンを使って書き換えられます。下のコードはgenerate関数を変更したバージョンです。このファイルはprogs/sieve1.goにあります。

func generate() chan int {
    ch := make(chan int)
    go func() {
        for i := 2; ; i++ {
            ch <- i
        }
    }()
    return ch
}

このバージョンでは、関数内ですべてのセットアップを行っています。まず出力チャネルをつくり、次にリテラル関数をゴルーチンとして起動して呼出元にチャネルを返します。これはすなわちゴルーチンを起動しその接続を返すという、並列処理のためのファクトリです。

goステートメントのリテラル関数表記によって、匿名の関数を作成し即座に呼び出しています。注意していただきたいのは、ローカル変数chはリテラル関数からもアクセス可能で、generate関数から抜けたあとも残り続けることです。同じ変更をfilter関数にも適用します。

func filter(in chan int, prime int) chan int {
    out := make(chan int)
    go func() {
        for {
            if i := <-in; i%prime != 0 {
                out <- i
            }
        }
    }()
    return out
}

その結果、sieve関数内のメインループはよりシンプルで処理が明瞭になりました。これも同様にファクトリに変更します。

func sieve() chan int {
    out := make(chan int)
    go func() {
        ch := generate()
        for {
            prime := <-ch
            out <- prime
            ch = filter(ch, prime)
        }
    }()
    return out
}

その結果、main関数とsieve関数とのやり取りは、素数を返すチャネルだけになりました。

func main() {
    primes := sieve()
    for i := 0; i < 100; i++ { // 最初の100個の素数を表示
        fmt.Println(<-primes)
    }
}

多重化

チャネルを使うことで多重化処理のコードをほとんど書くことなく、複数の独立しているゴルーチンに対してデータ配信することができます。今回のポイントとしては、サーバーへ送信するメッセージ内にあらかじめ返信用チャネルを含めておき、それを使ってクライアントに返信を行います。実際のクライアント/サーバプログラムは多くのコードからできていますが、ここでは分かりやすいように単純化した例で説明します。はじめにrequest型を定義し、そこに返信に使用するチャネルを埋め込みます。

type request struct {
    a, b   int
    replyc chan int
}

サーバ側は大した処理は行いません。整数の二項演算をするだけです。下のコードは演算を実行し、requestに返信します。

type binOp func(a, b int) int

func run(op binOp, req *request) {
    reply := op(req.a, req.b)
    req.replyc <- reply
}

まず型の宣言で、binOpという名前で、2つの整数パラメータを取り、整数を返す関数を定義しています。

server関数は無限ループしています。リクエストを受け取るたびに処理を行いますが、処理に時間がかかるとループ内でブロックしてしまうため、ゴルーチンを起動して処理を行わせます。

func server(op binOp, service chan *request) {
    for {
        req := <-service
        go run(op, req) // 待たない
    }
}

おなじみの方法でserverを作成・起動後、それと接続しているチャネルを返します。

func startServer(op binOp) chan *request {
    req := make(chan *request)
    go server(op, req)
    return req
}

次は簡単なテストコードです。「加算処理を行う関数」をfuncステートメントで作成し、それをパラメータにしてsartServerを呼び出してサーバを起動します。つづいてN個のリクエストを連続して送信します。すべてのリクエストを送信し終えてから応答を確認します。

func main() {
    adder := startServer(func(a, b int) int { return a + b })
    const N = 100
    var reqs [N]request
    for i := 0; i < N; i++ {
        req := &reqs[i]
        req.a = i
        req.b = i + N
        req.replyc = make(chan int)
        adder <- req
    }
    for i := N - 1; i >= 0; i-- { // 順序は関係ありません
        if <-reqs[i].replyc != N+2*i {
            fmt.Println("fail at", i)
        }
    }
    fmt.Println("done")
}

このプログラムの問題点はサーバがちゃんとシャットダウンしないということです。mainからリターンするときに処理に時間がかかってしまったゴルーチンがあると通信をブロックしてしまいます。これを解決するために別のチャネルquitserver関数に渡します。

func startServer(op binOp) (service chan *request, quit chan bool) {
    service = make(chan *request)
    quit = make(chan bool)
    go server(op, service, quit)
    return service, quit
}

server関数にquitチャネルを渡します。server関数では受け取ったチャネルを次のようにして使います。

func server(op binOp, service chan *request, quit chan bool) {
    for {
        select {
        case req := <-service:
            go run(op, req) // 待たない
        case <-quit:
            return
        }
    }
}

server内のselectステートメントは、caseでリストされた複数の通信のうち処理が続行可能な方を選択します。すべてがブロックされていれば、どれかが続行可能になるまで待機します。どちらも続行可能ならどちらかがランダムに選択されます。この場合、selectはquitメッセージを受信しリターンするまでの間、requestメッセージを受信し続けます。

あとはmain関数の最後にquitチャネルにデータを送信するよう変更します。

    adder, quit := startServer(func(a, b int) int { return a + b })

    quit <- true

まだまだGo言語のプログラミングおよび同期処理について語るところはありますが、このチュートリアルは基本の説明だけにとどめておきましょう。