Go メモ

Go

特徴

  • statically typed
    でも型推論や自動的に satisfy される interface のお蔭でお手軽に書けるのが素晴らしい。
  • concurrent language
    goroutine という軽量プロセスとチャネルベースの通信。CSP といや分かる人はわかるね。
    複数 CPU 使うのも runtime.GOMAXPROCS() を呼び出すだけというお手軽さ。
  • GC
    当然
  • 基本的にスタティックリンク
    ランタイム内包なので動かすの楽
  • defer
    RAII や with みたいなことが構文的にとても簡単に書けるようになっている。
  • アンチ例外
    panic で基本死ぬようになっている。色々な型の例外を飛ばすというのは、仕様の面で難しいので採用しなかったんだろう。
    後発言語の潔さ・割り切り感があっていい。
  • 言語仕様がコンパクト
    オブジェクト指向回りは複雑なことはしないが、十分に強力。
  • ある程度豊富なライブラリ
    http://golang.org/pkg/

ツールとか

Emacs 設定

;;; go-mode
(if (file-exists-p "/usr/local/go/misc/emacs/go-mode-load.el")
    (progn
      (add-to-list 'load-path "/usr/local/go/misc/emacs/")
      (require 'go-mode-load)
      (add-hook 'go-mode-hook
                (lambda ()
                  (setq truncate-lines t)
                  (setq tab-width 4)))
      ))

Git 設定

Go は整形もツールで自動的に行うと決まっている。
Git の pre-commit フックで整形忘れをチェックするスクリプトが提供されているのでコピーして使う。

$ cd /usr/local/go/misc/git
$ cp pre-commit $GIT/.git/hooks/

Quick introduction

Tutorials

以下の順で読むべし

1. How to Write Go Code
2. A Tour of Go
3. Effective Go

コーディング規約

フォーマットに関しては go fmt で自動整形することになっている。
どう書いてもいいけど最後は go fmt で整形してからコミットすること。
ちなみにインデントは1タブ。エディタでタブ文字の見た目の幅は好きに変えていい。

その他は命名規約くらい。大文字から始まるのがパブリックな API。
パッケージ名はソースディレクトリの最後のディレクトリ名と同じ、全部英小文字の文字列。

ディレクトリ構造

$GOPATH/
    src/
        github.com/user/
            proj1/
                *.go
            proj2/
                *.go
        cybozu/
            proj/
                *.go
    pkg/
        ビルドされたライブラリ
        なければ自動的にこのディレクトリは作られる
    bin/
        ビルドされた実行ファイル
        なければ自動的にこのディレクトリは作られる

つまり $GOPATH/src さえあればいい。

export GOPATH=/home/ymmt/go
go install all

で全実行ファイルが bin/ にインストールされる。

Packages

ライブラリのパッケージ名はファイルのあるディレクトリ名と同じにするのが基本。
例外は後述する main と、使用例を書くときの パッケージ名_test

foo/bar/zot.go
package bar

func Test() int { return 1 }

大文字から始まる名前が export されて他のパッケージに見える。
インポートすると基本は "パッケージ名.シンボル" になる。

package main

import "foo/bar"
import b "foo/bar"    // bar ではなく b で参照できる

all は標準も含め全パッケージ、std は全パッケージ、cybozu/... とすると cybozu/ 以下のサブパッケージになる。

$ go list cybozu/...
cybozu/newmath
cybozu/untar

Executable

バイナリを構成するには、パッケージ同様にディレクトリを用意する。ディレクトリ名がバイナリ名になる。
そのディレクトリには main 以外のパッケージは存在できない。

どれか一つのファイルで main 関数を定義しないといけない。パッケージ同様 init による初期化も可能。

"main.go"
package main

import {
    "flag"
}

func init() {
    // main の前に走る
}

func main() {
    flag1 := flag.Int("foo", 0, "Description of -foo")
    flag.Parse()
    // ...
}

VCS integration

github.com などから自動的にパッケージを持ってこれる。
社内 GitHub なら .git をつければ持ってこれる。

package main

import (
	"fmt"
	"github.com/edsrzf/mmap-go"
	"github.dev.cybozu.co.jp/hazama/gogogo.git"
)

func main() {
	fmt.Printf("Hello, world!\n")
}

go get はダウンロードしてライブラリもインストールもする。
ダウンロードだけなら go get -d とする。

go get は既にダウンロードしているものの更新チェックはしないが、
go get -u とすることで新しいバージョンがあれば更新してくれる。

$ go get all
  # dependency パッケージを含めて全部持ってきてインストールする
$ go get -d github.com/user/repo
  # パッケージを src にダウンロードしてくれる

Ignoring VCS directories

自動的に Subversion や Git や Mercurial のレポジトリを持ってきてくれるのはいいのだけど、
持ってきて Git につっこもうとすると管理用の .svn, .git, .hg 以下のファイルも
登録することになってしまう。

それでもいいやって人もいるかもしれない(git pull しやすいとかね)けど、そうでないなら
src/.gitignore で無視するようにするとよい。

$ mkdir src
$ cat >src/.gitignore <<EOF
.svn
.git
.hg
EOF
$ git add src/.gitignore

godoc

go doc PACKAGE でパッケージのドキュメントが読める。
godoc -http:8888 とかすると Web インタフェースでドキュメントが読める。
godoc godocgodoc の使い方もわかる。

ソースコードには普通にコメントを書いておくと doc comment になる。
コンベンションとして、関数名やパッケージ名から記述を始めること。

// Fprint formats using the default formats for its operands and writes to w.
// Spaces are added between operands when neither is a string.
// It returns the number of bytes written and any write error encountered.
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
...
}

// Package sort provides primitives for sorting slices and user-defined
// collections.
package sort

Examples

使用例を HTML ドキュメントに表示することができる。

典型的には "example_test.go" というファイルに "パッケージ_test" というパッケージ名で、
"Example関数名" で記述したい関数の例を書く。

"example_test.go"
package blob_test               // ディレクトリが "blob" なら

import {
    "cybozu/cydec/blob"         // パッケージが異なるので import 必須
}

func ExampleFoo() {
    // Foo 関数の使用例
}

func Example() {
    // パッケージの Overview に出てくる使用例
}

Test

ユニットテストの仕組みが備わっている。

http://golang.org/doc/code.html#Testing

$ go test cybozu/...

実は testing パッケージはベンチマークフレームワークも提供している。

並列実行するには GOMAXPROCS でプロセス数を指定して、テストでは t.Parallel() を呼んでおく。

func TestFoo(t *testing.T) {
    t.Parallel()
    // ...
}
$ GOMAXPROCS=4 go test -v cybozu/...

Basic Types

byte = uint8, rune = int32 = unicode code point

const a = 'a'                  // rune = int32
fmt.Printf("%T(%c)\n", a, a)   // "int32(a)"

変数は基本、ゼロ初期化される。グローバルも、new でも。

ユニコード

Go はソースコードも UTF-8 で書くし、stringバリデートされていないUTF-8。
なので以下のようにするとユニコードとしては不正なちぎれたバイト列ができてしまう。

s := "ユニコード"
s = s[1:]                // != "ニコード"

正しくはこう:

s := "ユニコード"
s = string([]rune(s)[1:])

Struct, Array, Slice, Map

struct, array はポインタでないとコピーされるので注意。
slice は array への参照を内部で持っているだけなので、値渡ししても shallow copy にとどまる。
map も同じ。

slice については以下をちゃんと読むこと。

// named struct
type Vertex struct {
    X int
    Y int
}

var (
    p = Vertex{}      // {0, 0}
)

func main() {
    v : new(Vertex)   // &{0, 0}

    a := [3]int {1, 2, 3}         // [3]int 型の array

    // unnamed struct literal
    v2 := []struct { i int  }{ {1}, {2}, {3} }

    slice := make([]int, 0, 5)    // len = 0, cap = 5
    fmt.Println(len(slice), cap(slice))

    // map literal
    m := map[string]int {
        "abc": 123,
        "def": 456,
    }
}

Built-in functions

Slice を拡張する append やコピーする copy などは組み込み関数になっている。

Switch

case の値は即値だけじゃなく、任意の式にできる。if-then-else の代わりになる。

自動フォールスルーはなくて明示しなければいけないので、case ラベルを並べても無駄。
代わりに "," で条件を列挙できる。

func foo(s string) {
    switch s {
    case "abc", "def":
        // do something
    case "ghi":                 // フォールスルーしない
    case "jkl":
        fallthrough             // 明示的にフォールスルー
    case "mno":
        // do something
    }
}

Method

ポインタで受けるようにしておけば、呼び出す側はポインタ型でなくてもポインタで実行される。
(のでコピーされないし、プロパティの変更も捨てられない)

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := &Vertex{3, 4}   // ポインタ型
    v.Scale(5)
    v2 := Vertex{3, 4}   // ポインタでないけど、ポインタ同様にメソッドを呼べる
    v2.Scale(5)
}

Variable arguments

func Log(v ...interface{}) {
    std.Output(2, fmt.Sprintln(v...))  // Output takes parameters (int, string)
}

Explicit type checking

二通りある。type switch と type assertion.

switch t := t.(type) {
case string:
    // ...
case int:
    // ...
default:
    // ...
}
s := t.(string)       // string にキャストできなければ死ぬ

s, ok := t.(string)  // string にキャストできなければ ok == false

Embedding (redirecting)

Go に継承はないのでメンバーに delegate するわけだが、delegate メソッドを
自動的につくる embed という仕組みがある。メンバーに名前をつけないだけ。

type AB struct {
    *A             // A へのポインタ
    *B             // B へのポインタ
}

func NewAB() *AB {
    return &AB{ &A{}, &B{} }
}

こうすると、AB には A のメソッドと B のメソッドと同じシグネチャの
delegate メソッドが自動的に実装される。

Channel

close できる。標準ではバッファサイズ1だけど、make で明示すれば2以上にできる。
入っている値の数は len, キャパシティは cap でわかる。

close(c)    // チャネル c をクローズ

Errors

エラーはインタフェース error を実装する。
自前で error を実装してもいいが、fmt.Errorf を使うのが手軽。

func Foo(i int) error {
    return fmt.Errorf("Invalid value: %d", i)
}

Panic

死ぬなら panic を呼び出す。
ライブラリは init 関数 以外では panic しない。

goroutine ひとつを abort したいケースでは panic から recover するようにする。
recover は defer しておかないとスキップされちゃうので、必ず defer 中で呼ぶ。

func safeDo() {
    defer func() {
        if err := recover(); err != nil {
            log.Println("work failed:", err)
        }
    }()
    do()
}

go safeDo()

Debugging

GDBでデバッグできます。最近の GDB なら標準でサポート。

SQL Database

database/sql は各種ドライバに共通な API を提供するパッケージ。JDBC みたいなもの。

チュートリアルはこちら:

注意点としては、uint64 の値はうまく insert や update ができないことかしら。
select で持ってくる分には問題ないようだけど、正式なところは知らない。

MySQL 向けには Go-MySQL-Driver が良いと思われ。
time.Time への自動マッピングをオプションで提供している。

A Tour of Go

Exercise 23

package main

import (
    "fmt"
    "math"
)

func Sqrt(x float64) float64 {
    z:= 1.0
    for {
        if t:= z - (z*z - x) / 2 * z; math.Abs(t - z) < 0.0001 {
            return t;
        } else {
            z = t
        }
    }
}

func main() {
    fmt.Println(Sqrt(2))
}

Exercise 35

package main

import "code.google.com/p/go-tour/pic"

func Pic(dx, dy int) [][]uint8 {
    s := make([][]uint8, dy)
    for i := range s {
        s[i] = make([]uint8, dx)
        for j := range s[i] {
            s[i][j] = uint8(i^j)
        }
    }
    return s
}

func main() {
    pic.Show(Pic)
}

Exercise 40

package main

import (
    "code.google.com/p/go-tour/wc"
    "strings"
)

func WordCount(s string) map[string]int {
    m := make(map[string]int)
    for _, word := range strings.Fields(s) {
        m[word] = m[word] + 1
    }
    return m
}

func main() {
    wc.Test(WordCount)
}

Exercise 43

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
    f0, f1 := 0, 1
    return func() int {
        f0, f1 = f1, f0+f1
        return f1
    }
}

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

Exercise 55

package main

import (
    "fmt"
    "math"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
    return "cannot Sqrt negative number: " + fmt.Sprint(float64(e));
}

func Sqrt(f float64) (float64, error) {
    if( f < 0 ) {
        return 0, ErrNegativeSqrt(f)
    }
    return math.Sqrt(f), nil
}

func main() {
    fmt.Println(Sqrt(2))
    fmt.Println(Sqrt(-2))
}

Exercise 69

package main

import (
    "code.google.com/p/go-tour/tree"
    "fmt"
)

func Walk(t *tree.Tree, ch chan int) {
    if( t == nil ) {
        return
    }
    Walk(t.Left, ch)
    ch <- t.Value
    Walk(t.Right, ch)
}

func Same(t1, t2 *tree.Tree) (result bool) {
    ch1 := make(chan int, 10)
    ch2 := make(chan int, 10)
    go Walk(t1, ch1)
    go Walk(t2, ch2)
    result = true
    for i := 0; i < 10; i++ {
        result = result && (<- ch1 == <- ch2)
    }
    return result
}

func main() {
    fmt.Println(Same(tree.New(1), tree.New(1)))
    fmt.Println(Same(tree.New(1), tree.New(2)))
}

必読パッケージ

以下に挙げるパッケージは各種処理で必須になるので読んでおくと効率的です。
できればソースも眺めておくとなおよし。

  • fmt
    Hello world!
  • io
    Reader や Writer インタフェース、io.Copy() などの I/O 処理全般。
    ストリーム末尾は io.EOF エラーが返るなど、知らないとコードが書けないレベル。
    io/ioutil も合わせて読んでおきたい。
  • os
    ファイルを扱うには必須。
  • strings
    文字列処理に。
  • flag
    コマンドライン引数の処理に。
  • log
    ログという名前に隠れて、exit したり panic したりする関数がある。

比較

 

C++

Python

Go

システムコール

自由に使える

かなり使える

Python より少々少ない

型システム

複雑すぎるルール

なにそれ

シンプル&型推論

非同期実行

頑張れば...

gevent や PyPy なら

得意

並列実行

本気出せる

無理ぽ

悪くない

GC

GC は甘え

あり

あり

速さ

最速

遅い

かなり速い

ライブラリ

Web系のはあんまないね

超たくさん

Web系はたくさん

参考資料