参考
$ go version
go version go1.11 darwin/amd64
Goは、2009年にGoogleにより発表されたオープンソースのプログラミング言語です。
- シンプルな言語仕様
- Windows、OS X、Linuxなどの環境に合わせた実行ファイルを生成できるクロスコンパイル
- 並行処理のサポート
- 型がある
- 標準のコーディング規約がある
- 巨大なコードでも高速にコンパイルできて大規模開発にも適してる
- 自由で楽しい
ruby
と堅牢で安心なgo
- オブジェクト指向ではなく、関数型プログラミング
繰り返し構文はfor文しかなく、while文やdo/while文などもない。
ifの波括弧は省略できず、三項演算子もありません。
開発者による表現のばらつきを抑えてて、誰が書いてもだいたい同じような書き方になる。
Arrayに .count
、 .length
、 .size
とエイリアスの多い ruby
とは逆。
意図せぬエラーの原因になる暗黙の型変換などは排除。
使われていない変数、使われていないインポートはコンパイルエラー。
コードを追加することよりもコードを削除するほうが、影響範囲が読みにくく難しい。
try/catch
がなく、発生した例外を戻り値として呼び出し側に返す。
例外を返せないエラー時にはパニックとリカバという例外を制御する方法もある。
python
も ruby
もできないわけではない。タプルとか
ゴルーチンという軽量スレッドを用いて並行処理ができる。 Promise
のようなもの。
同時に実行されているゴルーチンの間ではチャネルという機能でデータをやりとりできる。
コマンド | 説明 |
---|---|
go build | プログラムのビルド |
go fmt | Goの規約に合わせてプログラムを整形 |
go get | 外部パッケージの取得 |
go install | プログラムのビルドとインストール |
go run | プログラムのビルドと実行 |
go test | テストやベンチマークの実行 |
go tool yacc | パーサをGoで出力するGo実装のyacc(パーサジェネレータ) |
godoc | ソースからドキュメントの生成 |
$ brew install go
各々shellにpath通す
$ echo 'export PATH=$PATH:/usr/local/opt/go/libexec/bin' >> ~/.zshrc
$ source ~/.zshrc
$ go version
go version go1.11 darwin/amd64
もしくはここからインストール
package main
import "fmt"
func main(){
fmt.Printf("Hello, world\n")
}
$ go run hello.go
hello world
$ go build hello.go
$ ls
hello hello.go
$ ./hello
hello world
64ビットのMac OS Xなら64ビットのMac OS Xで動くバイナリができる
他の環境でbuildしたいならオプションで指定する
この バイナリができる
ってのがとても良くて、環境作らなくて良いし、どこでも動くのでかなり強力!
標準のコーディング規約があって、開発者同士の嗜好を巡って無駄な言い争いが起こることを防いでる
$ go fmt hello.go
ソフトタブ(スペース2,4つ)がハードタブ(タブ)に変換された
ソフトタブが主流なのでキモく感じるけど、速さを追求して(スペース数分byteを無駄にしない)って事だと思う。
フォーマットしてくれるし規約として一貫性出るからむしろありがたい。
パッケージの説明をみたり、公式サイトと同様のインタフェースでlocalにサーバー立てれる(オフライン以外必要ないけど)
$ godoc fmt
$ godoc -http=":3000"
http://localhost:3000/
ファイルが増えたときは、パッケージを分割する場合があります。
その場合は、プロジェクトをきちんと構成し環境変数を指定する必要があります。
myprojectというプロジェクトの中でgosampleというパッケージを作成し、そのパッケージをmainパッケージから呼び出すよう構成してみます。
$ tree myproject
myproject
├── bin # go install時の格納先
├── pkg # 依存パッケージのオブジェクトファイル
└── src # プログラムのソースコード
$ cd myproject
$ export GOPATH=`pwd`
$ echo $GOPATH
/path/to/myproject
GOPATHがプロジェクトルートになり配下3つのディレクトリの命名規則を用いて依存関係を解決する
プロジェクトルートを .envrc
指定の方が便利かも
export GOPATH=`pwd`
Goでは,1つのパッケージは1つのディレクトリに格納します。
Message変数を持ったgosampleとそれをprintするmain.goを作ります。
package gosample
var Message string = "hello world"
package main
import (
"fmt"
"gosample"
)
func main() {
fmt.Println(gosample.Message)
}
$ cd $GOPATH
$ go run src/main/main.go
hello world
$ cd $GOPATH/src/main
$ go install # $GOPATH/binに入る
$ go build # その場にバイナリ作る
gitでリポジトリが公開されていればパッケージとして使用可能
rubygemsに公開する時くらいの心理障壁はない
├── bin
│ └── main
├── pkg
└── src
- ├── gosample
- │ └── gosample.go
+ ├── github.com
+ │ └── arakawamoriyuki
+ │ └── gosample
+ │ └── gosample.go
└── main
└── main.go
package main
import (
"fmt"
- "gosample"
+ "github.com/arakawamoriyuki/gosample"
)
func main() {
fmt.Println(gosample.Message) // hello world
}
あとはgithubでリポジトリ作って公開するだけ
$ cd $GOPATH/src/github.com/arakawamoriyuki/gosample
$ git init
$ git add .
$ git commit -m 'my first golang package'
$ git remote add origin https://github.com/arakawamoriyuki/gosample
$ git push origin master
go get
コマンドでプロジェクト内に展開します
$ cd $GOPATH/src
$ rm -rf github.com # 一旦けして
$ go get github.com/arakawamoriyuki/gosample # 取得
$ go run main/main.go # 実行してみる
Revision指定できないの??と思った方。鋭いです。お察しの通り__できません__(少なくとも現行バージョンの1.2では不可能です)。
パブリックなパッケージは常に後方互換性を持たせるべきで、異なる機能性を持たせたいときは、パッケージ自体の名前を変えて、import pathも変更するべき。
とはいえ依存パッケージ管理には需要があり、以下のようなツールがある。
- vgo
- dep
- Glide
- gom
Go & Versioning(vgo)を読んで大きな変更が入ったなと思った
Go 1.11で試験的な導入、Go 1.12で正式サポート
8月28日にリリースされたGo v1.11から試験的に利用可能な公式で作っている依存パッケージ管理ツールです。
v1.12
になった際には dep
から Modules (vgo)
に移行すると思われる。
- $GOPATHが不要になる
dep
では必要なvendorディレクトリが必要なくなる- パッケージ側はセマンティックバージョニングでタグ付けする
- go.modファイルに依存関係が書かれる
Modulesを利用する
$ export GO111MODULE=on
buildするとgo.modが作られて依存関係が書かれる
$ cd $GOPATH
$ go build src/main/main.go
module github.com/arakawamoriyuki/go-handson
require github.com/arakawamoriyuki/gosample v1.0.0 // indirect
go1.11のModulesを使って依存パッケージを管理する
Go 1.11 の modules・vgo を試す - 実際に使っていく上で考えないといけないこと #golang
Go 1.11 の modules・vgo を試す - 実際に使っていく上で考えないといけないこと #golang
dep は Go が公式に "実験的に" 作っている依存管理ツールです。glide など以前から存在していたツールからの migration の仕組みも持っており、dep への移行を促すプロジェクトも多いです。
package main
go run
ではmainパッケージのmain関数が実行される。
それ以外の、例えば gosample.go
にmain関数を置いて実行しても package main
ではないのでエラーになる (go run: cannot run non-main package
)
gosample.go
で package main
(と偽って)宣言してmain関数を用意したら実行できる模様。
区切りにカンマはいらない
GOPATH環境変数からパスを解決する
import (
"fmt"
"github.com/arakawamoriyuki/gosample"
)
インポートにはいくつかのオプションがある。以下の例では
文字列
はエイリアスになる(fmt.Println()
がf.Println()
).
は中の関数が展開される (strings.ToUpper()
がToUpper()
)_
は使用していないパッケージだと明示する (使用してなくてもコンパイルエラーにならない)
import (
f "fmt"
_ "github.com/wdpress/gosample"
. "strings"
)
型 | 説明 |
---|---|
uint8 | 8ビット符号なし整数 |
uint16 | 16ビット符号なし整数 |
uint32 | 32ビット符号なし整数 |
uint64 | 64ビット符号なし整数 |
int8 | 8ビット符号あり整数 |
int16 | 16ビット符号あり整数 |
int32 | 32ビット符号あり整数 |
int64 | 64ビット符号あり整数 |
float32 | 32ビット浮動小数 |
float64 | 64ビット浮動小数 |
complex64 | 64ビット複素数 |
complex128 | 128ビット複素数 |
byte | uint8のエイリアス |
rune | Unicodeのコードポイント |
uint | 32か64ビットの符号なし整数 |
int | 32か64ビットの符号あり整数 |
uintptr | ポインタ値用符号なし整数 |
error | エラーを表わすインタフェース |
文字列はダブルクォート
var Message string = "hello world"
ヒアドキュメントはバッククォート
var Message string = `first line
second line
third line`
型推論 (明らかにわかる型はコンパイラが推論して型宣言を省略できる)
message := "hello world"
//var 変数名 型 = 値
var message string = "hello world"
// 同じ型なら省略可能
var foo, bar, buz string = "foo", "bar", "buz"
var (
a string = "aaa"
b = "bbb"
c = "ccc"
d = "ddd"
e = "eee"
)
定数はconst、再代入不可になる
const Hello string = "hello"
Hello = "bye" // cannot assign to Hello
代入しない場合ゼロ値になる
var i int // 整数型のゼロ値 0 になる
型 | ゼロ値 |
---|---|
整数型 | 0 |
浮動小数点型 | 0.0 |
bool | false |
string | "" |
配列 | 各要素がゼロ値の配列 |
構造体 | 各フィールドがゼロ値の構造体 |
そのほかの型 | nil |
条件に丸括弧はいらない
三項演算子はない
a, b := 10, 100
if a > b {
fmt.Println("a is larger than b")
} else if a < b {
fmt.Println("a is smaller than b")
} else {
fmt.Println("a equals b")
}
基本的なfor
for i := 0; i < 10; i++ {
fmt.Println(i)
}
whileっぽいfor
n := 0
for n < 10 {
fmt.Printf("n = %d\n", n)
n++
}
無限ループ
for {
doSomething()
}
ループを終了する break
、スキップする continue
などは利用可能。
カンマで区切った複数の値も指定可能。
n := 10
switch n {
case 15:
fmt.Println("FizzBuzz")
case 5, 10:
fmt.Println("Buzz")
case 3, 6, 9:
fmt.Println("Fizz")
default:
fmt.Println(n)
}
caseが1つ実行されると次のcaseにうつらない。 fallthrough
キーワードで次にうつる事もできる。
n := 3
switch n {
case 3:
n = n - 1
fallthrough
case 2:
n = n - 1
fallthrough
case 1:
n = n - 1
fmt.Println(n) // 0
}
引数には型を指定、複数同じ型なら一つにまとめられる。
func sum(i, j int) {
fmt.Println(i + j)
}
戻り値は関数定義のあとに型指定。
func sum(i, j int) int {
return i + j
}
複数値を返せるので複数の型指定。
func swap(i, j int) (int, int) {
return j, i
}
名前付き戻り値で return
の後の値を省略できる。(代入された値を返す。代入されていなければ結果的にゼロ値を返す)
func div(i, j int) (result int, err error) {
if j == 0 {
err = errors.New("divied by zero")
return // return 0, errと同じ
}
result = i / j
return // return result, nilと同じ
}
無名関数
func(i, j int) {
fmt.Println(i + j)
}(2, 4)
変数に入れる事もできます。
var sum func(i, j int) = func(i, j int) {
fmt.Println(i + j)
}
可変長引数も利用できます。
func sum(nums ...int) (result int) {
// numsは[]int型
for _, n := range nums {
result += n
}
return
}
func main() {
fmt.Println(sum(1, 2, 3, 4)) // 10
}
goはエラーを戻り値で表現するため、try/catch
や throw
がありません。
自作のエラーは,errorsパッケージを使って表現します。
package main
import (
"errors"
"fmt"
"log"
)
func div(i, j int) (int, error) {
if j == 0 {
// 自作のエラーを返す
return 0, errors.New("divied by zero")
}
return i / j, nil
}
func main() {
n, err := div(10, 0)
if err != nil {
// エラーを出力しプログラムを終了する
log.Fatal(err)
}
fmt.Println(n)
}
複数の値を返す場合もエラーを最後にする慣習があるのでそれに従いましょう。
配列は固定長で、長さを指定する。
var arr1 [4]string
[...]
で暗黙的に長さの指定。
暗黙の型変換と組み合わせた例。
arr := [...]string{"a", "b", "c", "d"}
もちろん引数にも型と長さの指定をする必要があります。
func fn(arr [4]string) {
fmt.Println(arr)
}
func main() {
var arr1 [4]string
var arr2 [5]string
fn(arr1) // ok
fn(arr2) // コンパイルエラー
}
スライスという可変長配列も定義できる。
var s []string
s := []string{"a", "b", "c", "d"}
pythonのように値を部分的に切り出す事ができます。
s := []int{0, 1, 2, 3, 4, 5}
fmt.Println(s[2:4]) // [2 3]
fmt.Println(s[0:len(s)]) // [0 1 2 3 4 5]
fmt.Println(s[:3]) // [0 1 2]
fmt.Println(s[3:]) // [3 4 5]
fmt.Println(s[:]) // [0 1 2 3 4 5]
append
はスライスの末尾に値を追加し、その結果を返す組込み関数です。
s1 := []string{"a", "b"}
s2 := []string{"c", "d"}
s1 = append(s1, s2...) // s1にs2を追加
fmt.Println(s1) // [a b c d]
添字によるアクセスの代わりに range
を使用できます。
var arr [4]string
arr[0] = "a"
arr[1] = "b"
arr[2] = "c"
arr[3] = "d"
for i, s := range arr {
// i = 添字, s = 値
fmt.Println(i, s)
}
$ go run range.go
0 a
1 b
2 c
3 d
string
のキーに int
の値を格納するマップ
var month map[string]int = map[string]int{}
month := map[string]int{
"January": 1,
"February": 2,
}
キーの存在確認
_, ok := month["January"]
if ok {
// データがあった場合
}
マップからデータを消す
delete(month, "January")
Go にジェネリクスがなくても構わない人たちに対する批判について
JAVAのように配列の中やマップの中まで型定義できるジェネリクスは現時点でない模様。
interfacce{}
型を利用するらしい
Goはポインタを扱うことができます。
PHPでいう引数に&つけて明示的に参照渡すやつ。 function increment(&$var) {...}
int型も参照渡しできるし配列を値渡しもできる。
func callByValue(i int) {
i = 20 // 値を上書きする
}
func callByRef(i *int) {
*i = 20 // 参照先を上書きする
}
func main() {
var i int = 10
callByValue(i) // 値を渡す
fmt.Println(i) // 10
callByRef(&i) // アドレスを渡す
fmt.Println(i) // 20
}
明示的にポインタだと宣言できない言語(rubyもpythonもjavascriptも)が多い中、中で更新されてるか毎度疑わなくて良くなるので嬉しい。
引数受ける側も関数利用側もポインタ渡す/受けるなら *
もしくは &
つけないとコンパイルエラーになるので、副作用理解した上でプログラミングできる。
明示的なのはとても嬉しい。
ファイル開いたり、データベースにコネクション貼ったり、エラーが起きても起きなくても実行して欲しいものに書く。
defer
は延期という意味。
finaly
みたいなイメージで良いと思う。
func main() {
file, err := os.Open("./error.go")
if err != nil {
// エラー処理
}
// 関数を抜ける前に必ず実行される
defer file.Close()
// 正常処理
}
エラーは戻り値によって表現するのが基本だが、
配列やスライスの範囲外にアクセスした場合や、ゼロ除算をしてしまった場合などはエラーを返せない。
この状態をパニックという。
パニックで発生したエラーは recover
で拾えるので、deferで処理する事でエラー処理ができる。
func main() {
defer func() {
err := recover()
if err != nil {
// runtime error: index out of range
log.Fatal(err)
}
}()
a := []int{1, 2, 3}
fmt.Println(a[10]) // パニックが発生
}
パニックは自分で起こす事もできる。けど、基本的にエラーは関数の戻り値として呼び出し側に返しましょう。
a := []int{1, 2, 3}
for i := 0; i < 10; i++ {
if i >= len(a) {
panic(errors.New("index out of range"))
}
fmt.Println(a[i])
}
以下の関数はint型の id
と priority
を受け取る。
func ProcessTask(id, priority int) {
}
同じ int
型なので
引数の順番間違えてもコンパイル通っちゃう間違えやすいインターフェースになってる。
var id int = 3
var priority int = 5
ProcessTask(id, priority)
ProcessTask(priority, id) // 順番間違えてもコンパイル通る
場合に応じて独自の型を定義すると安全になる。
type ID int
type Priority int
func ProcessTask(id ID, priority Priority) {
}
var id ID = 3
var priority Priority = 5
ProcessTask(priority, id) // コンパイルエラー
構造体も独自の型として宣言可能。
構造体のプロパティはドットでアクセス可能
type Task struct {
ID int
Detail string
done bool
}
var task Task = Task{
ID: 1,
Detail: "buy the milk",
done: true,
}
fmt.Println(task.ID) // 1
fmt.Println(task.Detail) // "buy the milk"
fmt.Println(task.done) // true
var task Task = Task{} // Task型
var task *Task = &Task{} // Taskのポインタ型
ポインタ型ではない型は値渡しされる。
type Task struct {
ID int
Detail string
done bool
}
func Finish(task Task) {
task.done = true
}
func main() {
task := Task{done: false}
Finish(task)
fmt.Println(task.done) // falseのまま
}
ポインタ型は参照渡しされる。
func Finish(task *Task) {
task.done = true
}
func main() {
task := &Task{done: false}
Finish(task)
fmt.Println(task.done) // true
}
逆にポインタ型でなければ値渡しされる。
func Finish(task Task) {
task.done = true
}
func main() {
task := Task{done: false}
Finish(task)
fmt.Println(task.done) // false
}
構造体は組み込み関数 new
でゼロ値で初期化
type Task struct {
ID int
Detail string
done bool
}
var task Task = new(Task)
Goには構造体のコンストラクタにあたる構文がありません。
代わりにNewで始まる関数を定義し、その内部で構造体を生成するのが通例です。
func NewTask(id int, detail string) *Task {
task := &Task{
ID: id,
Detail: detail,
done: false,
}
return task
}
func main() {
task := NewTask(1, "buy the milk")
// &{ID:1 Detail:buy the milk done:false}
fmt.Printf("%+v", task)
}
とりあえずメソッドと関数の違い
関数 = 処理をまとめたもの
メソッド = 自身に対する操作をする関数。 this.name
とかプロパティを利用する関数。
メソッドはメソッド名の前にメソッドを定義したい型を指定する。
変数名は this
にあたいするものだとと読み替えてもいいかも。
func (変数名 メソッドを定義したい型) メソッド名() 戻り値の型 {
}
type Task struct {
ID int
Detail string
done bool
}
func NewTask(id int, detail string) *Task {
task := &Task{
ID: id,
Detail: detail,
done: false,
}
return task
}
// Task.Stringメソッドの宣言、taskの文字列表現を返す
func (task Task) String() string {
str := fmt.Sprintf("%d) %s %t", task.ID, task.Detail, task.done)
return str
}
// Task.Finishメソッドの宣言、taskを参照渡しして完了する
func (task *Task) Finish() {
task.done = true
}
func main() {
task := NewTask(1, "buy the milk")
fmt.Printf(task.String())
task.Finish()
fmt.Printf(task.String())
}
Javaの implements
のようなもので、
Taskに実装したString()というメソッドが定義されている事を強制する、などの用途に利用する。
実装すべき関数名が単純な場合は,その関数名にerを加えた名前を付ける慣習がある。
// .String()メソッドを実装している事をコンパイラに知らせる
type Stringer interface {
String() string
}
// この関数には.String()メソッドを実装しているオブジェクトを渡せる。
func Print(stringer Stringer) {
fmt.Println(stringer.String())
}
Print(task)
例えば、 fmt.Print
の引数は String()
を実装している事を interface
で強制しているので先ほど作った task
を渡せる。
type Stringer interface {
String() string
}
fmt.Print(task)
インターフェースではないけど ruby
の DateTime
とかもそうで、文字列じゃないのに puts
できるのは基底クラス Object
に to_s
が実装されているから。
すべての引数を受け付けるAny型が作れる
type Any interface {
}
func Do(any Any) {
// do something
}
書き方自体はこれと同じ
func Do(any interface{}) {
// do something
}
Goでは,継承はサポートされていません。
代わりにほかの型を「埋め込む」(Embed) という方式で構造体やインタフェースの振る舞いを拡張できます。
// 苗字と名前を持ったUser構造体
type User struct {
FirstName string
LastName string
}
// User.FullName()メソッドの定義
func (u *User) FullName() string {
fullname := fmt.Sprintf("%s %s", u.FirstName, u.LastName)
return fullname
}
// Userのコンストラクタ
func NewUser(firstName, lastName string) *User {
return &User{
FirstName: firstName,
LastName: lastName,
}
}
// Userを埋め込んだTask構造体を定義
type Task struct {
ID int
Detail string
done bool
*User // Userを埋め込む(Embed)
}
// Taskのコンストラクタ
func NewTask(id int, detail, firstName, lastName string) *Task {
task := &Task{
ID: id,
Detail: detail,
done: false,
User: NewUser(firstName, lastName),
}
return task
}
func main() {
task := NewTask(1, "buy the milk", "Jxck", "Daniel")
// TaskにUserのフィールドが埋め込まれている
fmt.Println(task.FirstName)
fmt.Println(task.LastName)
// TaskにUserのメソッドが埋め込まれている
fmt.Println(task.FullName())
// Taskから埋め込まれたUser自体にもアクセス可能
fmt.Println(task.User)
}
構造体( struct
)だけではなくインターフェース( interface
)も「埋め込む」(Embed) 事ができます
// 読み込みメソッドがある事をインターフェースで宣言
type Reader interface {
Read(p []byte) (n int, err error)
}
// 書き込みメソッドがある事をインターフェースで宣言
type Writer interface {
Write(p []byte) (n int, err error)
}
// 読み込みメソッドも書き込みメソッドもある事をインターフェースで宣言
type ReadWriter interface {
Reader
Writer
}
暗黙の型変換はできないけど明示的に型変換(キャスト)はできます。
var s string = "abc"
var b []byte = []byte(s) // string -> []byte
fmt.Println(b) // [97 98 99]
キャストに失敗した場合はパニックが発生します。
// cannot convert "a" (type string) to type int
a := int("a")
Type Assertionで型を調べる事ができます。
第一戻り値が元の値、第二戻り値が調べた結果です。
s, ok := value.(string) // Type Assertion
if ok {
fmt.Printf("value is string: %s\n", s)
} else {
fmt.Printf("value is not string\n")
}
Type Switchで型で分岐処理ができます。
switch v := value.(type) {
case string:
fmt.Printf("value is string: %s\n", v)
case int:
fmt.Printf("value is int: %d\n", v)
case Stringer:
fmt.Printf("value is Stringer: %s\n", v)
}
軽量スレッド、ゴルーチンを利用して非同期処理できる
以下は https://google.com
にリクエストを3回送る迷惑なコード。
同期処理の場合は、1度目のリクエスト完了後に2度目,3度目...と直列で続く。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
urls := []string{
"https://google.com",
"https://google.com",
"https://google.com",
}
for _, url := range urls {
res, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
fmt.Println(url, res.Status)
}
}
go
キーワードで非同期処理の場合は、並行してリクエストを行うのでその分早い。
package main
import (
"fmt"
"log"
"net/http"
"sync"
)
func main() {
wait := new(sync.WaitGroup)
urls := []string{
"https://google.com",
"https://google.com",
"https://google.com",
}
for _, url := range urls {
// waitGroupに追加
wait.Add(1)
// 取得処理をゴルーチンで実行する
go func(url string) {
res, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
fmt.Println(url, res.Status)
// waitGroupから削除
wait.Done()
}(url)
}
// 待ち合わせ
wait.Wait()
}
複数のゴルーチンのデータをやりとりしたい場合チャネルを利用する。
まずは組み込み関数 make
でチャネルの作り方と書き込み、読み込み方法について。
// stringを扱うチャネルを生成
ch := make(chan string)
// チャネルにstringを書き込む
ch <- "a"
// チャネルからstringを読み出す
message := <- ch
チャネルを利用して
HTTPリクエストを並行して発行し、早く取得されたステータスから順に受け取って処理しておく事ができる
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
urls := []string{
"https://google.com",
"https://google.com",
"https://google.com",
}
statusChan := make(chan string)
for _, url := range urls {
// 取得処理をゴルーチンで実行する
go func(url string) {
res, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
statusChan <- res.Status
}(url)
}
for i := 0; i < len(urls); i++ {
// waitする必要がなく、先に受け取ったチャネルから処理できる
fmt.Println(<-statusChan)
}
}
読み出しや書き込みイベント制御のためのselect文もあります。
主な用途はfor/select文とbreakを用いて実装するタイムアウト処理などに利用されます。
ch1 := make(chan string)
ch2 := make(chan string)
for {
select {
case c1 := <-ch1:
// ch1からデータを読み出したときに実行される
case c2 := <-ch2:
// ch2からデータを読み出したときに実行される
case ch2 <- "c":
// ch2にデータを書き込んだときに実行される
}
}
また、make
関数には同時に3つまで読み出されないかぎりチャネルに書き込まない制御をするバッファ付きチャネルや、
同時起動数制御などメッセージキューのような動作をしてくれるバッファ指定引数があります。
encoding/json
JSONから構造体、構造体からJSONに変換os
,io
ファイルの作成、書き込み、読み込みなどio/ioutil
ファイルの操作のラッパーnet/http
HTTPサーバ作成APItext/template
webに必要なhtmlのテンプレートエンジン
Gin
がデファクト?
- Gin
- Echo
- iris
- Goji
- beego
- Revel
- Martini
- Gorilla
httprouter
を使ってAPIサーバーを作って見ましょう。
├── bin
├── pkg
└── src
├── github.com
│ └── julienschmidt
│ └── httprouter
│ ├── LICENSE
│ ├── README.md
│ ├── params_go17.go
│ ├── params_legacy.go
│ ├── path.go
│ ├── path_test.go
│ ├── router.go
│ ├── router_test.go
│ ├── tree.go
│ └── tree_test.go
└── main
└── main.go
httprouter
をgo getします。
$ cd $GOPATH/src
$ go get github.com/julienschmidt/httprouter
main.go
を作成します。
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"github.com/julienschmidt/httprouter"
)
// /Hello/:langにハンドルされているHello関数
func Hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
lang := p.ByName("lang") // langパラメーターを取得する
fmt.Fprintf(w, lang) // レスポンスに値を書き込む
}
// /ExampleにハンドルされているExample関数
func Example(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
defer r.Body.Close() // Example関数が終了する時に実行されるdeferステートメント
// リクエストボディを読み取る
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
// リクエストボディの読み取りに失敗した => 400 Bad Requestエラー
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// JSONパラメーターを構造体にする為の定義
type ExampleParameter struct {
ID int `json:"id"`
Name string `json:"name"`
}
var param ExampleParameter
// ExampleParameter構造体に変換
err = json.Unmarshal(bodyBytes, ¶m)
if err != nil {
// JSONパラメーターを構造体への変換に失敗した => 400 Bad Requestエラー
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// 構造体に変換したExampleParameterを文字列にしてレスポンスに書き込む
fmt.Fprintf(w, fmt.Sprintf("%+v\n", param))
}
func main() {
router := httprouter.New() // HTTPルーターを初期化
// /HelloにGETリクエストがあったらHello関数にハンドルする
// :langはパラメーターとして扱われる
router.GET("/Hello/:lang", Hello)
// /ExampleにPOSTリクエストがあったらExample関数にハンドルする
router.POST("/Example", Example)
// Webサーバーを8080ポートで立ち上げる
err := http.ListenAndServe(":8080", router)
if err != nil {
log.Fatal(err)
}
}
APIサーバーを実行します。
$ cd $GOPATH/src/main
$ go run main.go
コードを確認しながら試して見ましょう。
$ curl http://localhost:8080/Hello/golang
Hello golang
$ curl -XPOST -d "{\"id\": 1, \"name\": \"arakawa\"}" http://localhost:8080/Example
{ID:1 Name:arakawa}
/FizzBuzz/:num
で FizzBuzz APIを作って見ましょう。
$ curl http://localhost:8080/FizzBuzz/1
1
$ curl http://localhost:8080/FizzBuzz/2
2
$ curl http://localhost:8080/FizzBuzz/3
Fizz!
$ curl http://localhost:8080/FizzBuzz/4
4
$ curl http://localhost:8080/FizzBuzz/5
5
$ curl http://localhost:8080/FizzBuzz/6
Buzz!
$ curl http://localhost:8080/FizzBuzz/7
7
$ curl http://localhost:8080/FizzBuzz/15
FizzBuzz!