Go メモ
Go
特徴
- statically typed
でも型推論や自動的に satisfy される interface のお蔭でお手軽に書けるのが素晴らしい。 - concurrent language
goroutine という軽量プロセスとチャネルベースの通信。CSP といや分かる人はわかるね。
複数 CPU 使うのもruntime.GOMAXPROCS()
を呼び出すだけというお手軽さ。 - GC
当然 - 基本的にスタティックリンク
ランタイム内包なので動かすの楽 - defer
RAII や with みたいなことが構文的にとても簡単に書けるようになっている。 - アンチ例外
panic で基本死ぬようになっている。色々な型の例外を飛ばすというのは、仕様の面で難しいので採用しなかったんだろう。
後発言語の潔さ・割り切り感があっていい。 - 言語仕様がコンパクト
オブジェクト指向回りは複雑なことはしないが、十分に強力。 - ある程度豊富なライブラリ
http://golang.org/pkg/
ツールとか
- パッケージ同梱
misc
ディレクトリ下に emacs や vim の設定ファイルが入ってる - gocode
https://github.com/nsf/gocode
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
。
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
による初期化も可能。
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 godoc
で godoc
の使い方もわかる。
ソースコードには普通にコメントを書いておくと 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関数名" で記述したい関数の例を書く。
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系はたくさん |