Create a gist now

Instantly share code, notes, and snippets.

@hayajo /00.md
Last active Sep 7, 2017

What would you like to do?
NDS#36 Go言語入門

Go言語入門


目次


Go言語


Go言語とは

Googleによって開発されたオープンソースのプログラミング言語


特徴

  • シンプル
  • コンパイル・実行速度が早い
  • 充実した標準パッケージ
  • 並行処理が容易
  • ポータビリティ
  • 強力なツール
  • ダックタイピング
  • イントロスペクション
  • 型推論
  • GC
  • ʕ◔ϖ◔ʔ

注目度

Google トレンド

実装やサービス

  • Google App Engine
  • Docker, Packer, Consul/Serf, Terraform
  • Vitess
  • CloudFlare

etc.


開発環境構築


インストール

ダウンロードページから環境にあったバイナリ/インストーラでインストール

クロスコンパイル環境を構築する場合はGo のクロスコンパイル環境構築 - Qiitaを参照


GOPATH

Goで開発では環境変数GOPATHの設定が必要

$ export GOPATH=$HOME
$ export PATH="$GOPATH/bin:$PATH"
$ go env GOPATH
...
  • GOPATH

    • ワーキングディレクトリを設定する
    • go getやgo installなど、goツールのベースパスとなる
    • $GOPATH配下のディレクトリ構成
      • bin コンパイル後に生成される実行ファイルの格納先
      • pkg コンパイル後に生成されるパッケージの格納先
      • src ソースコードの保存先でパッケージごとにサブディレクトリを作成
    • importの参照先は$GOROOT/pkgまたは$GOPATH/pkgの、アーキテクチャ配下のパスとなる

GOROOT

$ go env GOROOT
  • GOROOT

    • 標準ライブラリを探すためのベースパスが設定される
    • JAVA_HOMEのようなもの
    • 基本的には***設定不要***だが、Goインストール先を標準のパスから変更した場合は設定が必要

Go言語基礎


Hello Go

http://play.golang.org/p/AzJcxtdp5A

  • セミコロン不要
  • 実行にはmain()を含むmainパッケージが必要
  • importで外部パッケージをインポート

変数

http://play.golang.org/p/p6w6hUF36J

  • 型は最後に書く
  • := で型推論
  • ポインタあり(ただしポインタ演算なし^3
  • new(T)は初期化(ゼロ初期化^4)してポインタを返す
  • makeは参照型(スライス、マップ、チャネル)を初期化して値を返す

  • 論理値型
  • 数値型
  • 文字列型
  • 配列型
  • スライス型
  • 構造体型
  • ポインタ型
  • 関数型
  • インタフェース型
  • マップ型
  • チャネル型

宣言済みの型

bool       : 論理値(true, false)
uint8      : 符号なし 8ビット 整数
uint16     : 符号なし 16ビット 整数
uint32     : 符号なし 32ビット 整数
uint64     : 符号なし 64ビット 整数
int8       : 符号あり 8ビット 整数
int16      : 符号あり 16ビット 整数
int32      : 符号あり 32ビット 整数
int64      : 符号あり 64ビット 整数
float32    : IEEE-754 32-ビット 浮動小数値
float64    : IEEE-754 64-ビット 浮動小数値
complex64  : float32の実数部と虚数部を持つ複素数
complex128 : float64の実数部と虚数部を持つ複素数
byte       : uint8の別名
rune       : int32の別名
string     : 文字列(値は不変)

実装に依存する宣言済みの型

uint    : 32または64ビット
int     : uintと同じサイズ
uintptr : ポインタの値を格納するのに充分な大きさの符号なし整数

if

http://play.golang.org/p/5EXfndS7Z2

  • 条件の () は省略可能
  • 三項演算子はない

for

http://play.golang.org/p/-8jQIbBH3c

  • ループはforのみ
  • ループの外に抜けるにはcontinue, break
  • ネストされたループを抜けるにはラベルを使用する
  • ++(--)は後置のみ。また式ではないため値へ評価されないので a := b++ などはNG

switch

http://play.golang.org/p/aB3QEjr6f7

  • break不要
  • caseにはカンマ区切りのリストを指定可能
  • caseには式も指定可能

関数

http://play.golang.org/p/2qva2t_zcp

  • funcで関数を宣言
  • ...Tで可変個引数(引数は[]Tとなる)
  • interface{}(空インタフェース型)で任意の型を受け取る
  • スライスを可変個引数として渡す場合は、末尾に...をつける
  • 関数は複数の値を返すことが可能
  • 関数の返り値は全て無視かすべて受け取る。一部を無視したい場合は_(ブランク識別子)を使用する
  • クロージャ

エラー処理

http://play.golang.org/p/3MoYcyhfFk

  • (一般的には)戻り値としてのエラー値を検査することでエラー処理を行う

余裕があれば解説


リソースのクリーンアップ

http://play.golang.org/p/Dpz2S_eGT3

  • deferでfinallyと似たようなことができ、関数の実行を遅延させることができる
  • deferに指定された関数は、それが書かれている関数が終了する時点で実行される

パニックとリカバー

http://play.golang.org/p/gPCTNNd11P

  • panic(), recover()でtry-catch-finally的な実装が可能
  • deferで呼ばれる関数内であればrecover()は機能するので、呼び出す関数にrecover()を記述することで例外処理をまとめることができる
  • が、panic()は明らかに回復手段がない場合にだけ使用し、また、回復するのが絶対的に安全で無い限りrecover()は使用しない

配列

http://play.golang.org/p/mM7iMigla-

  • [...]で要素数を推論
  • 配列は通常の値
  • 別の配列への代入は要素のコピーとなる(ポインタではない)

スライス

http://play.golang.org/p/CTnQ6_0NZk

  • スライスは配列内の連続した領域への参照(配列に対するビュー)
  • スライスは配列へのポインタ(Data)、アクセスできる要素の数(Len)、最大のスライスの大きさ(Cap)をもつ

ref. research!rsc: Go Data Structures


スライスの拡張と縮小

http://play.golang.org/p/_1tqLU6E99

  • スライスに要素を追加するにはappendを使用する
  • スライスのCapが十分な大きさを持たない場合は、追加する値を含んだ新しい配列へのポインタをDataとするので注意が必要
  • スライスにスライスをappendする場合は...を使用する
  • スライスの縮小は、縮小したCapの新しいスライスを作り、既存のスライスから新しいスライスへ値をコピーする

配列とスライスのイテレーション

http://play.golang.org/p/L9JWrwhBf1

  • 配列やスライスのイテレーションにはrangeを使用する

マップ

http://play.golang.org/p/ivM5DvEyed

  • 他言語でいうところの連想配列や辞書
  • make()で作成する。宣言やnew()で作ると初期化されないので注意
  • キーに使用できる型は等価演算子が定義されていなければならない。詳しくはこちらを参照
  • マップのイテレートにもrangeを使用する

型システム


構造体とメソッド

http://play.golang.org/p/LZQaLcVV6P

  • 型レベルでのアクセス指定子はないが、下記のルールでパッケージレベルでアクセス制御される
    • 大文字で始まるトップレベルの型、メソッド、変数名、構造体のフィールドはエクスポートされる(public)
    • それ以外はエクスポートされない(private)
  • 継承はないのでprotectedなフィールドはない
  • フィールドにはタグを指定でき、reflectパッケージから参照可能
  • 既存の型に名前付けをして別の型(具象型)を定義できる
  • メソッドのレシーバは値型でもポインタ型でも定義できる
  • レシーバが値型の場合は、値・ポインタ両方に対して呼び出し可能
  • レシーバをポインタとする場合の指針
    • レシーバのフィールドに対して変更を行う場合
    • レシーバが巨大な場合
    • メソッドの一貫性を持たせる場合

型埋め込み

http://play.golang.org/p/5sUMUiABwm

  • 型埋め込みによる暗黙の委譲(継承ではない)
  • レシーバはフィールドであり、フィールドを含む構造体ではないので注意
  • 同名のメソッドの場合は呼び出すフィールドを明示する

インタフェース

http://play.golang.org/p/9peN5uMc80

  • インタフェースはメソッドの集合
  • インタフェースでダックタイピングを実現
  • 型埋め込み同様、インタフェースを埋め込むことが可能。インタフェースの継承と等価 ^1
  • インタフェースが定義しているメソッドをすべて実装している限り、インタフェース型を持つ変数へどんな型でも代入可能
  • 直接のフィールドへのアクセスが必要ない場合は、構造体自身ではなくパブリックのメソッドを定義したインタフェースだけを公開することで実装を隠蔽する ^2

キャストと型アサーション、型スイッチ

http://play.golang.org/p/xJkHdWz_0T

  • キャストはCと同じような感じ。細かいルールはこちら
  • 型アサーションは実行時にチェックされる
    • 特定のインタフェースを実装した型なのか
    • 期待した型なのか
  • インタフェース型じゃない値はinterface{}にキャストしたあとで型アサーションを行う
  • 型スイッチで複数の型に対する検査を行う

並行処理


ゴルーチン

http://play.golang.org/p/y4Vzz1nybW

  • ゴルーチンは軽量スレッドのようなもの(≠スレッド)
  • go ステートメントで生成

ゴルーチンの同期とバックグランド実行

http://play.golang.org/p/fMKP2_5S_v

  • ゴルーチンの同期にはミューテックスや条件変数(syncパッケージ)を使用する方法もあるが、チャネルを使ったほうが自然

http://play.golang.org/p/3_Id04WVw1

  • バックグランドでの処理実行の終了待ちにはsync.WaitGroupを使用すると便利

チャネル

http://play.golang.org/p/UUEhlFRinu

  • ゴルーチン間の通信を提供。双方向パイプに近い。メッセージ型を指定して利用する
  • make()で作成する

http://play.golang.org/p/kWGHEZO1cF

  • チャネルはバッファリング可能

http://play.golang.org/p/5Bzs1oo37T

  • 複数チャネルを扱う場合はselectを使用する

並列化

runtime.GOMAXPROCS(runtime.NumCPU())
  • 同時に利用可能なCPU数はデフォルトで1。並列度を上げる場合は環境変数GOMAXPROCSもしくはruntime.GOMAXPROCS()で設定する
  • GOMAXPROCSを上げすぎてもスレッド間のコンテキストスイッチのオーバーヘッドで性能が下がる場合もあるので調整が必要

パッケージ


可視性とファイル分割

// user/user.go
package user

type User interface {
  Name() string
}

func NewUser(name string) User {
  if name != "" {
    return &user{name}
  }
  return new(guest)
}
// user/types.go
package user

type user struct {
  name string
}

func (u user) Name() string {
  return u.name
}

type guest struct {}

func (_ guest) Name() string {
  return "Guest"
}
  • 大文字で始まるトップレベルの型、メソッド、変数名、構造体のフィールドはエクスポートされ、他のパッケージからアクセスできる(public)
  • それ以外はエクスポートされず、同一パッケージからしかアクセス出来ない(private)
  • パッケージはディレクトリ単位で構成される
  • パッケージは複数ファイルに分割できる(上記user/user.goとuser/types.goは同じuserパッケージとなる)

パッケージのインストール

$ go get github.com/codegangsta/martini
$ ls $GOPATH/pkg/darwin_amd64/github.com/codegangsta
inject.a martini.a

go get でサードパーティパッケージの取得とインストールを行う


パッケージのビルド

$GOPATH
└── src
    ├── user
    │   ├── types.go
    │   └── user.go
    └── userapp
        └── main.go

ビルド

$ go install user

go install でパッケージのビルド(go build)と$GOPATHへのインストールを行う

$GOPATH
├── pkg
│   └── darwin_amd64
│        └── user.a
└── src
    ├── user
    │   ├── types.go
    │   └── user.go
    └── userapp
        └── main.go

ビルド(mainパッケージ)

$ go install userapp

ビルド対象がmainパッケージの場合は実行可能なバイナリが生成される

$GOPATH
├── bin
│   └── userapp
├── pkg
│   └── darwin_amd64
│        └── user.a
└── src
    ├── user
    │   ├── types.go
    │   └── user.go
    └── userapp
        └── main.go

ファイル名や出力先を変えたい場合は go build -o <OUTPUT_PATH> を実行する

$ go build -o /tmp/fooapp userapp
$ ls /tmp
fooapp

クロスコンパイル

$ GOOS=windows GOARCH=386 go install userapp

クロスコンパイルは環境変数GOOSとGOARCHを指定する(クロスコンパイル用の環境構築が必要)

$GOPATH
├── bin
│   ├── windows_386
│   │   └── userapp.exe
│   └── userapp
├── pkg
│   ├── darwin_amd64
│   │   └── user.a
│   └── windows_amd64
│       └── user.a
└── src
    ├── user
    │   ├── types.go
    │   └── user.go
    └── userapp
        └── main.go

go buildでも同様に環境変数を指定して実行する

$ GOOS=linux GOARCH=arm go build userapp

テスト


テスト概要

  • go testとtestingパッケージを使用してテストを行う
  • ファイル名は_test.goで終わるようにする
  • Testで始まり(t *testing.T)のシグニチャをもつ関数を順番に実行する

テスト対象は "パッケージ" のuserパッケージ


テストコード

// user_test.go
package user

import(
	"testing"
	"reflect"
	"runtime"
)

func isType(t *testing.T, got interface{}, expected interface{}) {
	gotT := reflect.TypeOf(got)
	expectedT := reflect.TypeOf(expected)
	if gotT != expectedT {
		_, file, line, _ := runtime.Caller(1)
		t.Errorf("\nLocation: %s:%d\nError: got %s, expected %s", file, line, gotT, expectedT)
	}
}

func TestNewUser(t *testing.T) {
	g := NewUser("")
	isType(t, g, new(guest))

	u := NewUser("hayajo")
	isType(t, u, new(guest)) // fail
}

テスト実行

$ ls
types.go  user.go  user_test.go
$ go test
--- FAIL: TestNewUser (0.00 seconds)
        user_test.go:14:
                Location: /<PWD>/user_test.go:23
                Error: got *user.user, expected *user.guest
FAIL
exit status 1
FAIL    _/<PWD>      0.006s

デバッグ


デバッグ概要

  • デバッグにはgdb(>= 7.1)を使う

  • Goの最適化を無視するために -gcflags '-N -l' をつけてビルドする

    $ go build -gcflags '-N -l'
  • gdbでruntime-gdb.pyをロードしてデバッグを行う(~/.gdbinitに書いてもOK)

    (gdb) source <$GOROOT/pkg/runtime/runtime-gdb.py>

デバッグの簡単な流れ(1/9)

// myapp.go
package main

import "fmt"

func main() {
	a := make([]int, 5)
	fmt.Println(a[9]) // panic
}

デバッグの簡単な流れ(2/9)

$ go build -gcflags '-N -l' myapp.go

-gcflags '-N -l' をつけてビルドする


デバッグの簡単な流れ(3/9)

$ gdb myapp

デバッグ開始


デバッグの簡単な流れ(4/9)

(gdb) source <PATH_TO_runtime-gdb.py>

runtime-gdb.pyをロードする(必要な場合)


デバッグの簡単な流れ(5/9)

(gdb) break runtime.panicindex

runtime.panicindex にブレークポイントを設定


デバッグの簡単な流れ(6/9)

(gdb) run
...
Breakpoint 1, runtime.panicindex () at ...

プログラム実行


デバッグの簡単な流れ(7/9)

(gdb) up
#1  0xXXXXXXXXXXXXXXXX in main.main () at /.../myapp.go:7
7       fmt.Println(a[9])

upして呼び出し元に戻る


デバッグの簡単な流れ(8/9)

(gdb) info locals
a =  []int = {0, 0, 0, 0, 0}

ローカル変数の確認


デバッグの簡単な流れ(9/9)

(gdb) p $len(a)
$1 = 5
(gdb) p $cap(a)
$2 = 5

lenとcapの確認


サンプル


コマンドラインオプションの処理

http://play.golang.org/p/y3PqV9Wxjy

コマンドラインオプションをパースするサンプル。


ファイルの読み込み/書き込み

http://play.golang.org/p/xsZKcYtSCo

ファイルにデータを書き込み、行番号付きで出力するサンプル。


YAMLのエンコード/デコード

http://play.golang.org/p/Ur3-FPoRQC

標準パッケージではないためplay.golang.org上で実行できない。コピーして手元で実行しよう!

YAMLデータからstructに、structからYAMLデータに変換するサンプル。


JSONのエンコード/デコード

http://play.golang.org/p/n4QLhnjUMN

JSONデータからstructに、structからJSONデータに変換するサンプル。


データベースへの接続

http://play.golang.org/p/MQnDCBzxkX

標準パッケージではないためplay.golang.org上で実行できない。コピーして手元で実行しよう!

SQLiteデータベースを作成し、テーブル作成・データ登録・検索を行うサンプル。


HTTPクライアント/サーバー

http://play.golang.org/p/9DS0jq3yoa

ウェブカウンタを起動して結果を取得するサンプル。


文字コードのエンコード/デコード

http://play.golang.org/p/s8IfCZ3jeW

標準パッケージではないためplay.golang.org上で実行できない。コピーして手元で実行しよう!

文字コードのエンコード/デコードのサンプル


情報源

# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
config.vm.box = "hashicorp/precise64"
targets = "windows-amd64 darwin-amd64 linux-arm"
config.vm.provision "shell", inline: <<-EOS
set -x
go version
if [ $? -ne 0 ]; then
apt-get update
apt-get install -y build-essential gdb git mercurial bzr
cd /usr/local/src
hg clone --verbose -u release https://code.google.com/p/go && \
cd go/src && \
./all.bash && \
for t in #{targets}; do
GOOS=${t/-*/} GOARCH=${t/*-/} ./make.bash --no-clean
done
cd /usr/local/bin
find /usr/local/src/go/bin -maxdepth 1 -type f -exec ln -sf {} . \\;
fi
EOS
end
Owner

hayajo commented Mar 16, 2014

マップを追加しました

Owner

hayajo commented Sep 23, 2014

構成を変更しました

Owner

hayajo commented Sep 26, 2014

STORYBOARDS用に体裁を整えました

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment