Skip to content

Instantly share code, notes, and snippets.

@hayajo
Last active March 8, 2020 16:05
Show Gist options
  • Star 69 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save hayajo/9559874 to your computer and use it in GitHub Desktop.
Save hayajo/9559874 to your computer and use it in GitHub Desktop.
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

  • 型は最後に書く
  • := で型推論
  • ポインタあり(ただしポインタ演算なし1
  • new(T)は初期化(ゼロ初期化2)してポインタを返す
  • 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

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

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

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の確認


Footnotes

  1. #変数 "実際にはunsafeパッケージをつかえばポインタ演算可能ですが忘れましょう"

  2. #変数 "新たな変数を宣言した場合やnew(T)、makeで値を生成した場合は対応する型のゼロ値に初期化されます。bool型ならfalse、int型なら0、string型なら空文字、ポインタやスライス、チャネルならnilとなります。"

  3. #インタフェース "インタフェースコンポジション"

  4. #インタフェース "サンプルは同一パッケージ(main)なのでアクセス可能"

サンプル


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

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
@hayajo
Copy link
Author

hayajo commented Mar 16, 2014

マップを追加しました

@hayajo
Copy link
Author

hayajo commented Sep 23, 2014

構成を変更しました

@hayajo
Copy link
Author

hayajo commented Sep 26, 2014

STORYBOARDS用に体裁を整えました

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