ちなみに

火曜日の空は僕を押しつぶした。

シュッと golang に入門する話

photo by ajstarks

なんか最近みんな書いてる golangOSS へのコントリビュートチャンスも増えてきました。自分でバリバリ書くには時間も自信もない。でも、バグ修正くらいならやってみたい。それくらいの目的のために最低限必要な知識を書いてみました。

この記事では、自分ではバリバリ書けないけれど、golangOSS プロジェクトには貢献したいという人向けに、出来るだけシュッと学べるように重要なポイントのみ絞って紹介していきます。

初心者向けの優しい解説記事という訳ではないので、自分で調べるためのキッカケとしての読み方を想定しています。また、自分自身が想定読者のため、間違ったことを書いている可能性もあります。という逃げも書いておきます。

開発環境

基本的に Mac OSX + HomeBrew + Atom を前提とします。といいつつ、golangAtom もすべてのプラットフォームで動くはずなので、HomeBrew の部分だけ、各プラットフォームに置き換えてもらえると良さそうです。

golang

$ brew install go
$ echo "export GOPATH=~/.go" >> ~/.zshrc

golang では言語自体にパッケージマネージャの機能があり、go get URL でパッケージを取得できます。その際に GOPATH に指定したディレクトリ以下に配置されます。GOPATH は特にしばりはなく、好きな場所を指定するといいようです。Ruby 畑出身の僕は rbenv っぽい感覚で ~/.go を指定していますが、ghq を使い始めてからは普通のリポジトリもこの下に入れているので、若干失敗した感は否めません。

Atom

コードを書いている時間のほとんどを Vim と一緒に過ごしてきた僕ですが、昨年のβ公開時より Atom に移行しました。まだ、設定ファイルや、簡単な修正の場合は Vim を使うこともありますが、基本的には Atom に満足していて、メインで使っています。

golang の言語サポート自体はデフォルトパッケージとして付属しているので、go-plusautocomplete-plus だけインストールします。

$ apm install autocomplete-plus go-plus

もちろん Settings からワンクリックでインストールすることも出来ます。

autocomplete-plus

https://atom.io/packages/autocomplete-plus

Atom には autocomplete という補完用のパッケージが付属していますが、このパッケージでは補完のためにキー入力が必要です。autocomplete-plus をインストールすると、例えば . を入力したときなど、補完が可能な時は自動的に候補を表示してくれるようになります。

autocomplete-snippets を入れるとスニペットも候補として表示してくれるようになるので、こちらもおすすめです。

go-plus

https://atom.io/packages/go-plus

言語サポートではシンタックスハイライトやスニペットの提供をしてくれるのだけれど、この go-plu ではさらに gocode を使った補完や gofmt での整形、 go-imports での import の自動挿入などなど、golang の便利なツール群と Atom の連携を実現してくれます。正直にわかにとってはこれがないとコードが書けないくらい便利です。

入門

とりあえず A Tour of Go をひと通りやれば良さそうです。 基本的なことはほぼ全て網羅しているので、これをやるだけで golang 力が上がります。

あとは必要なところの ドキュメント をそのつど読めばなんとかなります。 Mac でドキュメント読むには Dash for Mac がおすすめ。

サンプルコードはすべてそのままコピペで動くようにしています。 そのため本質と関係のないコードも書いてありますが了承ください。

実行方法

例えば sample.go というファイルにコードを記述したとすると、以下の方法でコンパイルして実行できます。

$ go build sample.go
./sample

実は go run を使うと、コンパイルと実行を同時にやってくれるので、ぱっと実行するにはこちらの方がおすすめです。

$ go run sample.go

サードパーティのパッケージを取得するには go get を使います。

例えば、termbox-go は以下のようにして取得します。

$ go get github.com/nsf/termbox-go

変数

golang の変数は var 変数名 型 のように宣言することできます。 また、初期化子を指定することで宣言と同時に値を代入できます。

複数の変数を定義する場合で、型が同じ場合は最後に型を書けば全ての変数がその型であることを指定できます。

そして := を使うことにより、型推論をさせることも出来ます。型を明示しないといけない場合以外は、この方法で定義しておけば間違いなさそうです。 余談ですが := をなんと読むかは諸説あります。(see also: := 演算子をなんと呼ぶのか問題)

golang では定義した変数は必ず使わなければいけず、コンパイル時にエラーになります。_ という名前の変数だけは特別にこのルールが適用されません。

package main

import "fmt"

func main() {
    var name string
    name = "Bob"

    fmt.Println(name) //=> Bob

    var name2 string = "Alice"

    fmt.Println(name2) //=> Alice

    var num, num2 int = 1, 2

    fmt.Println(num, num2) //=> 1 2

    isChecked := true

    fmt.Printf("%v", isChecked) //=> true
}

基本型がいくつかあります。

  • bool
  • string // byte のスライス
  • int int8 int16 int32 int64
  • uint uint8 uint16 uint32 uint64 uintptr
  • byte // uint8 の別名
  • rune // int32 の別名、Unicode のコードポイントを表す
  • float32 float64
  • complex64 complex128

int8 は 8bit の符号付き整数、unit8 は 8bit の符号なし整数で、後はこの法則に従います。

uint は環境によって 32bit もしくは 64bit の符号なし整数となります。intuint と同じサイズの符号付き整数です。 しかし、uint が 32bit であった場合も、unit32 とは別の型になるので、明示的な型変換が必要です。 型変換は 型名(値) と書きます。

package main

import "fmt"

func main() {
    var a uint32
    var b uint = 5

    // a = b error!
    a = uint32(b)
    fmt.Println(a) //=> 5
}

また、golang では文字列を表現する方法が string[]rune の2種類あり、前者は文字を byte のスライスとして扱い、後者は rune のスライスとして扱います。 byte は 8bit しか表さないのに対して、runeUnicode 文字そのものを表します。また、string の range() を取ると rune が返るため、「文字」毎にループを回すということが実現出来ています。 スライスrange については後述します。

package main

import "fmt"

func main() {
    s := "日本語"
    r := []rune(s)

    fmt.Printf("%#U\n", s[0]) //=> U+00E6 'æ'
    fmt.Printf("%#U\n", r[0]) //=> U+65E5 '日'
}

配列 と マップ

golang では配列を [サイズ]型 と書きます。また {} を使って初期化することも出来ます。初期化されていない要素は勝手にデフォルトの初期値が入っています。

package main

import "fmt"

func main() {
    var array1 [5]int
    fmt.Println(array1) //=> [0 0 0 0 0]

    array1[2] = 5
    fmt.Println(array1) //=> [0 0 5 0 0]

    array2 := [3]string{"one", "two", "three"}
    fmt.Println(array2) // => [one two three]

    fmt.Println(array2[1]) //=> two

  array3 := [...]string{"面倒だったら", "サイズを省略", "することも出来る"}
  fmt.Println(array3) // [面倒だったら サイズを省略 することも出来る]
  fmt.Println(len(array3)) //=> 3
}

いわゆる連想配列は map を使って定義します。

package main

import "fmt"

func main() {
    var map1 map[string]int = map[string]int{}
    map1["one"] = 1
    fmt.Println(map1)        //=> map[one:1]
    fmt.Println(map1["one"]) //=> 1

    map2 := map[int]bool{1: true, 2: false}
    fmt.Println(map2[2]) //=> false
}

スライス

golang の配列は直感に反してポインターではなくて、実際に配列自体を表しています。でも、毎回値をコピーするのは非効率。そこでスライスが存在します。 スライスは、配列へのポインタ、配列のサイズ、配列のキャパシティ を持っており、スライス間では配列のデータ自体は共有されます。 (see also: Go言語のスライスを理解しよう)

スライスを作る方法は3つあります。

1つめは配列から作る方法で s := array[:] と書きます。

2つめはリテラルで書く方法で s := []int{1, 2, 3} と書きます。

3つめは make を使った方法で、make([]string, 3, 5) のようにサイズとキャパシティを指定できます。 キャパシティを省略した場合はサイズと同じ長さになります。

package main

import "fmt"

func main() {
    a := [...]int{1, 2, 3, 4, 5}
    fmt.Printf("%#v\n", a) //=> [5]int{1, 2, 3, 4, 5}
    s1 := a[:]
    fmt.Printf("%#v\n", s1) //=> []int{1, 2, 3, 4, 5}

    s2 := []string{"cat", "dog"}
    fmt.Printf("%#v\n", s2) //=> []string{"cat", "dog"}

    s3 := make([]bool, 2, 5)
    fmt.Printf("%#v\n", s3) //=> []bool{false, false}
    fmt.Println(len(s3))    //=> 2
    fmt.Println(cap(s3))    //=> 5
    s3 = s3[:cap(s3)]
    fmt.Println(len(s3)) //=> 5

    s4 := make([]uint, 3)
    fmt.Println(cap(s4)) //=> 3
}

また、スライスを再スライスすることも出来ます。再スライスによってスライスの一部だけを取り出すことが出来ます。 再スライスした場合でも参照しているメモリ領域は同じため、片方を変更するとどちらにも影響があります。

再スライスは スライス[開始位置:終了位置] と書きます。片方を省略した場合は、それぞれの端までを指定したことになります。

package main

import "fmt"

func main() {
    s1 := []int{1, 2, 3, 4, 5}
    fmt.Printf("%#v len=%d, cap=%d\n", s1, len(s1), cap(s1))
    //=> []int{1, 2, 3, 4, 5} len=5, cap=5
    s2 := s1[1:3]
    fmt.Printf("%#v len=%d, cap=%d\n", s2, len(s2), cap(s2))
    //=> []int{2, 3} len=2, cap=4

    fmt.Printf("%#v\n", s1[:3]) //=> []int{1, 2, 3}
    fmt.Printf("%#v\n", s1[2:]) //=> []int{3, 4, 5}

    s3 := s2[:1]
    s3[0] = 10
    fmt.Printf("%#v\n", s2) //=> []int{10, 3}
}

新しいメモリ領域にスライスの一部をコピーしたい場合は copy を使います。

package main

import "fmt"

func main() {
    s1 := []int{1, 2, 3, 4, 5}
    s2 := make([]int, 2)
    copy(s2, s1[1:3]) //=> []int{2, 3}
    fmt.Printf("%#v\n", s2)

    s2[0] = 5
    fmt.Printf("s1: %#v\n", s1) //=> s1: []int{1, 2, 3, 4, 5}
    fmt.Printf("s2: %#v\n", s2) //=> s2: []int{5, 3}
}

定数

定数は const を使って定義します。定数は 文字、文字列、真偽値、整数 のみを扱えます。 ただし型が指定出来ないので定数が使われるコンテキストに合わせて型が決まります。桁あぶれに注意。

package main

import "fmt"

const (
    One   = 1
    Two   = 2
    Three = 3
)

func main() {
    fmt.Println(One)   //=> 1
    fmt.Println(Two)   //=> 2
    fmt.Println(Three) //=> 3
}

値が連番になっている場合は、以下のような書き方も出来ます。

package main

import "fmt"

const (
    One = iota + 1
    Two
    Three
)

func main() {
    fmt.Println(One)   //=> 1
    fmt.Println(Two)   //=> 2
    fmt.Println(Three) //=> 3
}

制御文

iffor、switch` などおなじみのものが使えます。括弧は省略します。

if

if はお馴染みの書き方ですが、条件式の中で変数を定義出来るのが特徴です。条件式で定義した変数は if のスコープの中だけで有効です。

package main

import "fmt"

func main() {
    a := 5

    if a == 5 {
        fmt.Println("a is 5")
    } else {
        fmt.Println("a is not 5")
    }
    //=> a is 5

    if twice := a * 2; twice == 5 {
        fmt.Println("a * 2 = 5")
    } else if twice == 10 {
        fmt.Println("a * 2 = 10")
    } else {
        fmt.Println("otherwise")
    }
    //=> a * 2 == 10
    // fmt.Println(twice) error!
}

for

for にはいくつかの書き方があります。

C 言語のような if 初期化; 条件; 制御 {} のような書き方も出来ますし、for 条件 {} のように while の代わりに使うことも出来ます。また、for {} と書けば無限ループになります。 また、for i, n := range(array) {} のように range() と合わせるとイテレータのようなことも出来ます。i には 0 オリジンのが連番、n には値が入ります。それぞれ _ という名前にすることで省略できます。

package main

import "fmt"

func main() {
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }

    a := 10
    for a >= 0 {
        fmt.Println(a)
        a--
    }

    b := 0
    for {
        if b > 10 {
            break
        }
        b++
    }

    array := [...]int{1, 2, 3, 4, 5}
    for i, n := range array {
        fmt.Printf("%d: %d\n", i, n)
    }

    s := "日本語"
    for _, c := range s {
        fmt.Printf("%#U\n", c)
    }
}

switch

switch もお馴染みの書き方ですが、複数の条件を書ける、標準ではフォールスルーしない、式を書けるなどの特徴があります。

package main

import "fmt"

func main() {
    a := 2

    switch a {
    case 1:
        fmt.Println("a is 1")
    case 2:
        fmt.Println("a is 2")
    default:
        fmt.Println("otherwise")
    }

    switch a {
    case 1, 2, 3, 4:
        fmt.Println("a is 1 or 2 or 3 or 4")
    default:
        fmt.Println("otherwise")
    }

    switch a {
    case 1:
        fallthrough // フォールスルーさせる
    case 2:
        fmt.Println("a is 1 or 2")
    }

    switch {
    case a == 1:
        fmt.Println("a is 1")
    case a == 2:
        fmt.Println("a is 2")
    }
}

関数

関数は func 名前(引数) 戻り値 {} という書き方をします。戻り値を指定している場合は return が必須です。 複数の値を返すことも出来ます。

package main

import (
    "fmt"
    "math"
)

func add(a, b int) int {
    return a + b
}

func print(num int) {
    fmt.Println(num)
}

func pow(i, n int) (int, error) {
    if n > 10 {
        return 0, fmt.Errorf("n は 10 以下で指定してください")
    }
    return int(math.Pow(float64(i), float64(n))), nil
}

func main() {
    sum := add(1, 2)
    print(sum)

    p, err := pow(10, 100)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("result: %d", p)
    }
}

名前付きの戻り値というものもあって、以下のように sum がすでに定義された状態として扱え、return の引数も省略できます。

package main

import "fmt"

func add(a, b int) (sum int) {
    sum = a + b
    return
}

func main() {
    sum := add(1, 2)
    fmt.Println(sum)
}

golang の関数はファーストクラスファンクションになっているので、無名関数を作って変数に代入することもできます。また、レキシカルスコープであるのでクロージャを作ることも出来ます。

package main

import "fmt"

func mkCounter(init int) func() int {
    count := init
    return func() int {
        count++
        return count
    }
}

func main() {
    add := func(a, b int) int {
        return a + b
    }

    fmt.Println(add(1, 2)) //=> 3

    counter := mkCounter(5)
    fmt.Println(counter()) //=> 6
    fmt.Println(counter()) //=> 7
    fmt.Println(counter()) //=> 8
}

エラー処理

複数戻り値の関数の例を抜粋して再掲します。

package main

import (
    "fmt"
    "math"
)

func pow(i, n int) (int, error) {
    if n > 10 {
        return 0, fmt.Errorf("n は 10 以下で指定してください")
    }
    return int(math.Pow(float64(i), float64(n))), nil
}

func main() {
    p, err := pow(10, 100)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("result: %d", p)
    }
}

この例では error 型の戻り値を返しています。golang では Java などでいう 例外 が存在しないので、自分でエラーを返す必要があります。 定義した変数は必ず使わなければいけないルールがあるため、この方法でエラーを返すと必ずエラー処理をする必要があり、より安全なコードになることが期待出来ます。 標準ライブラリの関数も同様の方法でエラーを返します。

構造体

golang にはクラスの概念がありません。代わりに構造体を使ってオブジェクトのようなことを実現します。 構造体は type 名前 struct {} という書き方で定義でき、いくつかのフィールドを持つことができます。

A{1, 2} のように初期化して、各フィールドには a.X のように . を使ってアクセスできます。

package main

import "fmt"

type A struct {
    X int
    Y int
}

func main() {
    a := A{1, 2}
    fmt.Printf("X: %d\n", a.X) //=> X: 1
    fmt.Printf("Y: %d\n", a.Y) //=> Y: 2

    a.X = 5
    fmt.Printf("%#v\n", a) //=> main.A{X:5, Y:2}
}

メソッドを定義することも出来ます。少し独特なので最初は気持ち悪いかもしれませんが、そのうち慣れます。

基本は func (構造体) メソッド名(引数) 戻り値 {} のように書くと、指定した構造体のメソッドを定義出来ます。 またここで指定した変数を使って呼び出し元の構造体にアクセスすることが出来ます。 つまり、person.setName("Alice") では pperson が、nameAlice が格納されます。

package main

import "fmt"

type Person struct {
    name string
    age  int
}

func (p Person) greet() {
    fmt.Printf("Hi, my name is %s, %d years old.\n", p.name, p.age)
}

func (p *Person) setName(name string) {
    p.name = name
}

func (p *Person) setAge(age int) {
    p.age = age
}

func main() {
    person := &Person{"Bob", 18}
    person.greet()

    person.setName("Alice")
    person.setAge(28)
    person.greet()
}

(p Person)(p *Person) という2種類の書き方がされているのに気付いたかたもいらっしゃるかと思います。

golang にもポインタが存在していて、C言語などと同じように & でアドレスを表し、* で値を取り出せます。 構造体の場合は、構造体のポインタから直接フィールドやメソッドを呼び出せるようになっているので、簡単にポインタを扱えます。 この例では person := &Person{"Bob", 18} としているので、person には Person 構造体のインスタンスへのポインタが格納されていることになります。

では、なぜメソッドの定義でポインタとそうでないものがあるのでしょうか。golang では基本的には値渡しがなされます。 つまり func (p Person) ... という定義では、p は呼び出し元の構造体をコピーしたものが渡されてしまいます。 よって、いくらメソッドの中でフィールドの値を更新したところで、呼び出し元の構造体のフィールドは更新されないということになってしまいます。 そこで、func (p *Person) ... という指定をすることによって、ポインタで扱うことにすれば、参照渡しとなるので、呼び出し元のフィールドの更新を実現出来るのです。

また、以下のように構造体の定義の先頭に別の構造体のインスタンスを配置することで、継承のようなことも実現できます。

package main

import "fmt"

type Programmer struct {
    language string
}

func (p Programmer) coding() {
    fmt.Printf("%s を書いています\n", p.language)
}

func newProgramer(langeage string) *Programmer {
    return &Programmer{langeage}
}

type Person struct {
    *Programmer
    name string
    age  int
}

func (p Person) greet() {
    fmt.Printf("Hi, I'm %s, %d years old.\n", p.name, p.age)
}

func newPerson(name string, age int, language string) *Person {
    return &Person{
        Programmer: newProgramer(language),
        name:       name,
        age:        age}
}

func main() {
    bob := newPerson("bob", 18, "Java")
    bob.greet()  //=> Hi, I'm bob, 18 years old.
    bob.coding() //=> Java を書いています

    alice := newPerson("alice", 28, "Scala")
    alice.greet()  //=> Hi, I'm alice, 28 yeard old.
    alice.coding() //=> Scala を書いています
}

インターフェース

ダックタイプ とは、例えば「がーがーと鳴くもの」を「アヒル」とみなすという風に、あるメソッドを持つオブジェクトを型によらず同じものであると扱う手法です。 Ruby などのように型が存在しない場合は簡単に実現出来ますが、型のある golang でダックタイプのようなものを実現したいときにはどうすればいいのでしょうか。

golang にはインターフェースという仕組みがあり、これを用いるとダックタイプを実現出来ます。

package main

import "fmt"

type Person interface {
    greet()
}

type Man struct {
    name string
    age  int
}

func (p Man) greet() {
    fmt.Printf("Hi, my name is %s, %d years old.\n", p.name, p.age)
}

type Woman struct {
    name string
}

func (p Woman) greet() {
    fmt.Printf("Hi, my name is %s, 18 years old.\n", p.name)
}

func doGreet(person Person) {
    person.greet()
}

func main() {
    bob := &Man{"Bob", 18}
    doGreet(bob)

    alice := &Woman{"Alice"}
    doGreet(alice)
}

ここでは Person というインターフェースを定義しており、ManWoman がこのインターフェースを実装しています。 doGreet 関数は Person インターフェースを実装している構造体を引数に取っており、ManWoman どちらのインスタンスも受け付けます。

パッケージ

golang でもよくあるパッケージでのネームスペースの管理が出来ます。

これまで package main と書いていたのは main パッケージの中にコードを書いていたことになります。 新しく another パッケージを導入するときには以下のようなディレクトリ構成にします。

$ tree .
.
├── another
│   └── another.go
└── main.go

another パッケージは以下のように書いてみます。

package another

func NewA(value int) *A {
    return &A{value}
}

type A struct {
    value int
}

func (a A) Value() int {
    return a.value
}

func newb(value int) *b {
    return &b{value}
}

type b struct {
    value int
}

func (b b) Value() int {
    return b.value
}

ここではあえて camelCaseCamelCase を混ぜています。 実は golang ではこの違いには意味があります。 1文字目が大文字、つまり CamelCase にした場合は、パッケージの外部に公開され、camelCase の場合はパッケージの中に秘匿されるというルールがあり、この場合は構造体 A や関数 NewA は外部に公開され、構造体 b や関数 newB は外部には公開されません。

このパッケージを使うには以下のように相対パスでパッケージを指定します。

package main

import (
    "fmt"

    "./another"
)

func main() {
    a := another.NewA(5)
    fmt.Println(a.Value())

    // b := newB(10) error!
}

ただし、注意点があって、GOPATH 以下に配置されているプロジェクトの場合は相対パスでの指定は出来ません。GOPATH を基準としてパスで指定する必要があります。 例えば GitHubホスティングしているプロジェクトの場合は以下のようになるでしょう。

package main

import (
    "fmt"

    "github.com/User/Repo/another"
)

func main() {
    a := another.NewA(5)
    fmt.Println(a.Value())
}

並行処理

この辺りからだんだん説明があやしくなります。

golang は言語機能として並行処理をサポートしています。以下のように goroutine を使えば通常の関数を並行して実行することが出来ます。 例では定義した関数を使用していますが、無名関数を使う事もできます。その場合は go func() { ... }() のように定義した関数を呼び出してやる必要があります。

package main

import (
    "fmt"
    "time"
)

func loop(name string) {
    for i := 0; i < 5; i++ {
        fmt.Printf("%s: %d\n", name, i)
        time.Sleep(1)
    }
}

func main() {
    go loop("a")
    go loop("b")
    for i := 0; i < 5; i++ {
        fmt.Printf("main: %d\n", i)
        time.Sleep(1)
    }
}
// main: 0
// a: 0
// b: 0
// main: 1
// a: 1
// b: 1
// main: 2
// a: 2
// b: 2
// main: 3
// a: 3
// b: 3
// main: 4
// a: 4
// b: 4

並行処理とは少し違いますが、defer を使うと関数の実行を遅延することが出来ます。いつまで遅延するかというと、現在の関数が終了するまで遅延します。

package main

import "fmt"

func main() {
    defer func() { fmt.Println("終了しました") }()
    fmt.Println("処理中です")
}

この例だと全く嬉しくないのですが、たとえば関数が終了したときにリソースを開放するなどの処理が簡単に書けるため非常にべんりです。

万能のように見える goroutine ですが、平行して実行されるということは、Thread のようにリソースの扱いに注意が必要です。 複雑なことをしようとすると sync などのパッケージを使って同期を取る必要も出てきます。(see also: Big Sky :: golang の sync パッケージの使い方)

チャネル

並行で走っている goroutine 間でデータのやりとりをするにはどうすればいいのでしょうか。

もちろんこの問題の解決策も言語機能として用意されています。それにはチャネルを使います。

チャネルは make を使って make(chan 型) のように作成します。指定した型をやり取りすることが出来るチャネルが生成されます。

package main

import "fmt"

func add(a, b int, ch chan int) {
    ch <- a + b
}

func main() {
    ch := make(chan int)
    go add(1, 5, ch)

    sum := <-ch
    fmt.Println(sum) //=> 6

    close(ch)
    _, ok := <-ch
    fmt.Println(ok) //=> false
}

add 関数をチャネルを使って書き換えてみました。ch <- a + b という書き方でチェネルに値を送信して、sum := <- ch でチャネルから値を受け取っています。 <- ch と書いた時点で、チャネルから値が送られてくるか、close(ch) のようにしてチャネルが閉じられるまで待つことが出来ます。 また、2つ目の戻り値を見るとチャネルが閉じているかどうかを確認することが出来ます。

range を使えばチェネルから連続して送られてくる値を受け取ることが出来ます。ただし、適切にチャネルをクローズしてやらないとデッドロックを起こしてプログラムがクラッシュしてしまいます。

package main

import "fmt"

func main() {
    ch := make(chan int)

    go func() {
        for i := 0; i < 5; i++ {
            ch <- i
        }
        close(ch)
    }()

    for i := range ch {
        fmt.Println(i)
    }
}
//=> 0
//=> 1
//=> 2
//=> 3
//=> 4

select を使うと複数のチャネルを待ち受けることが出来ます。今回は外側のループを抜けるためにラベルを指定しています。

package main

import "fmt"

type Hub struct {
    outCh  chan int
    quitCh chan bool
}

func fibonacci(count int, hub *Hub) {
    a, b := 1, 1
    for i := 0; i < count; i++ {
        hub.outCh <- a
        a, b = b, a+b
    }
    hub.quitCh <- true
}

func main() {
    hub := &Hub{
        outCh:  make(chan int),
        quitCh: make(chan bool),
    }
    go fibonacci(10, hub)

LOOP:
    for {
        select {
        case n := <-hub.outCh:
            fmt.Println(n)
        case <-hub.quitCh:
            break LOOP
        }
    }
}

テスト

golang のテストは標準添付の testing パッケージを使います。あまりのシンプルさに他の言語からきた場合は各言語のテスティングフレームワークとの落差に驚かれるかもしれません。

例えば先ほどの another パッケージのテストを書く場合は以下のようにします。ファイル名を 任意の名前_test.go とする必要があります。

package main

import (
    "testing"

    "./another"
)

func TestAnotherA(t *testing.T) {
    a := another.NewA(5)
    if a.Value() != 5 {
        t.Errorf("a.Value() should be 5")
    }
}

実行は go test を使います。

$ go test another_test.go
ok      command-line-arguments  0.003s

無事通ったことを確認できました。引数を省略したときはカレントディレクトリの xxx_test.go をまとめて実行してくれます。

TextXXX という名前ので *testing.T 型の引数を取るメソッドを実行してくれます。よくある assert などは存在せず、if などでチェックして、問題がある場合は t.Errorf() を呼ぶとテストに失敗するという仕組みです。

テストについてはこの記事がとても参考になりました。

まとめ

  • Atom + go-plus が快適
  • A Tour of Go をやるといい
  • 補完が効きまくるし、エラーはコンパイラが拾ってくれるので、読めさえすればなんとかなる