Posted at

Go言語 文法まとめ

More than 1 year has passed since last update.


はじめに

Go言語の文法についてまとめました。


参考

訳のほうはだいぶ古くなっていて内容が追加されたりしたものが反映されていないので、できれば原文を読んだ方がいいと思います。


プログラムの実行

プログラムの実行は



  1. mainパッケージの初期化

  2. パッケージ中のmain関数の呼び出し

の順で行われ、main関数からreturnした時点で終了する。

main関数は引数、返り値を持たない。

func main() {

}


パッケージの初期化

パッケージの初期化は


  1. インポートするパッケージの初期化

  2. パッケージ変数への初期値の代入


  3. init関数の呼び出し

の順で行われる。init関数は引数、返り値を持たず、プログラム中で呼び出すことはできない。パッケージに複数のinit関数がある場合は順不同で実行される。

func init() {

}


パッケージ

package main


パッケージのインポート

import "fmt"

インポートしたパッケージの関数や定数には.を使ってアクセスできる。

import (

"fmt"
"math/rand"
)

func main() {
fmt.Println("My favorite number is", rand.Intn(10))
}


コメント


1行のコメント

//からその行の終わりまで

// コメント


複数行のコメント

/*から*/まで

/*

複数行
コメント
*/


名前


アンダースコア

名前をアンダースコア_にすると使わない値を捨てることができる。関数内で未使用の変数があると怒られるが、これを使えば回避できる。


エクスポート

大文字で始まる名前は外部パッケージから参照できる。

const ExportedConst = 1

var ExportedVar string

type ExportedType int

func ExportedFunc() {
}


予約語

break        default      func         interface    select

case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var


演算子、区切り文字

+    &     +=    &=     &&    ==    !=    (    )

- | -= |= || < <= [ ]
* ^ *= ^= <- > >= { }
/ << /= <<= ++ = := , ;
% >> %= >>= -- ! ... . :
&^ &^=


演算子の優先順位

同じ優先順位の演算子は左から結合される。たとえば、x / y * z(x / y) * zとなる。



  1. * / % << >> & &^


  2. + - | ^


  3. == != < <= > >=

  4. &&

  5. ||


変数

値を入れずに初期化した場合、その型のゼロ値が入る。関数内で宣言したのに使っていない変数があるとコンパイル時エラーになるので注意。

var i int

var U, V, W float64
var k = 0
var x, y float32 = -1, -2
var (
i int
u, v, s = 2.0, 3.0, "bar"
)
var re, im = complexSqrt(-1)
var _, found = entries[name]


省略形式

関数の中かifforswitchで変数を宣言するときに使うことができる。

i, j := 0, 10

f := func() int { return 7 }
ch := make(chan int)
r, w := os.Pipe(fd)
_, y, _ := coord(p)


ゼロ値

値を設定しないで初期化した変数には型ごとのゼロ値が入る。


  • 論理値 false

  • 整数 0

  • 浮動小数点 0.0

  • 文字列 ""

  • ポインタ、関数、インターフェイス、スライス、チャネル、マップ nil


定数

const Unit = 1

const a, b, c = 3, 4, "foo"

func main() {
const world = "世界"
}


連番の定数を作るiota

()を使ってまとめたconst内で定数の値にiotaを使うと、0から1ずつ増える連番の定数を作ることができる。const ()の外に出ると0に戻る。また、const ()内で定数の値を省略した場合は直前のものと同じ値になる。下の例ではこれを利用してiotaを毎回書かずに連番を設定している。

const (

c0 = iota // 0
c1 = iota // 1
c2 = iota // 2
)

const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)


型によって値とそれに対するメソッドを定めることができる。


型の定義

既存の型から新しく別の型を作ることができる。

type (

Point struct{ x, y float64 }
polar Point
)

別の型と扱われることを利用して、基本型にメソッドを定義することができる。

type TimeZone int

const (
EST TimeZone = -(5 + iota)
CST
MST
PST
)

func (tz TimeZone) String() string {
return fmt.Sprintf("GMT%+dh", tz)
}


型エイリアス

型に別名を付けることができ、完全に同じ型と扱われる。

type (

nodeList = []*Node
Polar = polar
)


型変換

型が演算子で始まる場合には()でくくる。

float32(2.718281828)

(*Point)(p)
(<-chan int)(c)


string[]byte[]rune間の変換


整数値→string

対応するUTF-8文字列になる。

string('a')  // "a"

string(-1) // "\ufffd" == "\xef\xbf\xbd"
string(0xf8) // "\u00f8" == "ø" == "\xc3\xb8"
type MyString string
MyString(0x65e5) // "\u65e5" == "日" == "\xe6\x97\xa5"


[]bytestring

string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø"

string([]byte{}) // ""
string([]byte(nil)) // ""

type MyBytes []byte
string(MyBytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø"


[]runestring

string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"

string([]rune{}) // ""
string([]rune(nil)) // ""

type MyRunes []rune
string(MyRunes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"


string[]byte

[]byte("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}

[]byte("") // []byte{}

MyBytes("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}


string[]rune

[]rune(MyString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4}

[]rune("") // []rune{}

MyRunes("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4}


基本型

以下の型は宣言済み。

bool byte complex64 complex128 error float32 float64

int int8 int16 int32 int64 rune string
uint uint8 uint16 uint32 uint64 uintptr


論理値

論理値を表す型はboolで、値はtrueまたはfalse

true

false


文字列

文字列を表す型はstring。実体はbyteのスライスだが、値は不変(immutable)。つまり、一度作成すると中身を変更することはできない。

バイト単位での文字列sの長さはlen(s)で取得でき、各バイトをs[i]のように取得できるが、&s[i]のようにアドレスを取得することはできない。

文字列を表すには2通りの方法がある。

1つはダブルクォート"で囲む方法で、改行を含めることはできず、バックスラッシュ\によるエスケープは解釈される。

"\n"

""
"Hello, world!\n"
"日本語"
"\u65e5\U00008a9e"
"\xff\u00FF"

もう1つがバッククォート`で囲む方法で、バッククォート以外の改行を含めたすべての文字を書くことができる。バックスラッシュのエスケープは解釈されない。

`abc`  // 結果は"abc"と同じ

`\n
\n`
// 結果は"\\n\n\\n"と同じ
`日本語`


数値

uint8       符号なし  8-ビット 整数 (0 to 255)

uint16 符号なし 16-ビット 整数 (0 to 65535)
uint32 符号なし 32-ビット 整数 (0 to 4294967295)
uint64 符号なし 64-ビット 整数 (0 to 18446744073709551615)

int8 符号あり 8-ビット 整数 (-128 to 127)
int16 符号あり 16-ビット 整数 (-32768 to 32767)
int32 符号あり 32-ビット 整数 (-2147483648 to 2147483647)
int64 符号あり 64-ビット 整数 (-9223372036854775808 to 9223372036854775807)

float32 IEEE-754 32-ビット 浮動小数値
float64 IEEE-754 64-ビット 浮動小数値

complex64 float32の実数部と虚数部を持つ複素数
complex128 float64の実数部と虚数部を持つ複素数

byte uint8の別名(エイリアス)
rune int32の別名(エイリアス)


インクリメント、デクリメント

x++ // x += 1 と同じ

x-- // x -= 1 と同じ


整数

42


8進数

0を頭につけると8進数になる。

0644


16進数

0xを頭につけると16進数になる。

0xc0ffee


浮動小数点数

整数部・小数点・小数部・指数部を持つ。123.45e-2だと123が整数部、45が小数部、e-2が指数部。


ルーン

ルーンruneはUnicodeコードポイントを表す整数で、int32の別名。ルーン1つはシングルクォート'で囲んで表すことができる。

'a'

'ä'
'本'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'


ポインタ

ポインタは変数のメモリ上のアドレスを表す。int型の変数へのポインタの型は*intとなる。

&を使ってポインタを取得できる。

i := 42

p = &i

*でポインタが指す変数の値にアクセスできる。

fmt.Println(*p) // ポインタpを通してiから値を読みだす

*p = 21 // ポインタpを通してiへ値を代入する


構造体

構造体は名前と型を持つ要素であるフィールドの集まり。構造体のフィールドの値には.を使ってアクセスする。

type Vertex struct {

X int
Y int
}

func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}

構造体のポインタからフィールドにアクセスする際に*は不要。

func main() {

v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
}

構造体を初期化する際に省略したフィールドの値はゼロ値になる。フィールドのキーを書かないこともできるが、その場合すべてのフィールドの値を宣言されている順に書く必要がある。

var (

v1 = Vertex{1, 2} // キーを書かない場合
v2 = Vertex{X: 1} // Yはintのゼロ値の0
v3 = Vertex{} // XもYもintのゼロ値の0
p = &Vertex{1, 2} // &でポインタを取得できる
)


埋め込み

型名のみを宣言すると匿名フィールド(埋め込みフィールド)になる。これを型の構造体への埋め込みという。埋め込まれた型名をフィールド名として扱える。埋め込まれた型が持っているメソッドも構造体のメソッドとして使うことができる。

type t struct {

T1 // フィールド名はT1
*T2 // フィールド名はT2
P.T3 // フィールド名はT3
*P.T4 // フィールド名はT4
x, y int // フィールド名はx、y
}

以下の例はbufio.ReadWriterのもので、これはbufio.Readerbufio.Writerのポインタを埋め込んでいる。bufio.Readerio.Readerbufio.Writerio.Writerをそれぞれ実装している。この埋め込みによりbufio.ReadWriterReadメソッドとWriteメソッドを持つことになるため、io.Readerio.Writerio.ReadWriterのすべてのインタフェースを実装している。

type ReadWriter struct {

*Reader // *bufio.Reader
*Writer // *bufio.Writer
}


タグ

フィールドの宣言の後に文字列でタグを指定することができる。タグはkey:"value"の形式でスペース区切りで書く。通常、"を含むため`で囲む。

type s struct {

microsec uint64 `protobuf:"1"`
serverIP6 uint64 `protobuf:"2"`
}


配列

同一の型を持つ要素を並べたもの。あとから配列の長さを変えることはできない。そのため、代わりにスライスを使うのが一般的。

var a [2]string

a[0] = "Hello"
a[1] = "World"
primes := [6]int{2, 3, 5, 7, 11, 13}


スライス

配列の部分列への参照であり、可変長配列のように扱える。要素がstring型のスライスの型を[]stringと表す。スライスは配列へのポインタ、長さ、キャパシティの3つの情報から構成されている。長さは要素数で、キャパシティはスライスの最初の要素から数えたときの参照先の配列の要素数。長さ、キャパシティはそれぞれlencap関数で取得することができる。

スライスは配列・配列へのポインタ・スライスから作ることができる。s[low:high]sのインデックスがlowからhigh - 1までの要素を持つスライスができる。lowを省略すると0highを省略するとlen(s)と見なされる。

a := [5]int{1, 2, 3, 4, 5}

s := a[1:4] // s == []int{2, 3, 4}
a[2:] // a[2:len(a)]と同じ
a[:3] // a[0:3]と同じ
a[:] // a[0:len(a)]と同じ

make関数を使って新しくスライスを作ることができる。

a := make([]int, 5)    // len(a)=5, cap(a)=5

b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4


要素の追加

append関数を使う。スライスの要素は同じ型の要素しか追加できないが、[]byte型の変数に...をつけた文字列を追加することができ、文字列は[]byteとして追加される。

s0 := []int{0, 0}

s1 := append(s0, 2) // s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7) // s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...) // s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...) // s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

var t []interface{}
t = append(t, 42, 3.1415, "foo") // t == []interface{}{42, 3.1415, "foo"}

var b []byte
b = append(b, "bar"...) // b == []byte{'b', 'a', 'r'}


コピー

スライスの要素をcopy関数を使ってコピーすることができる。文字列を[]byteにコピーすることもできる。

var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}

var s = make([]int, 6)
var b = make([]byte, 5)
n1 := copy(s, a[0:]) // n1 == 6, s == []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:]) // n2 == 4, s == []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "Hello, World!") // n3 == 5, b == []byte("Hello")


マップ


初期化

m := make(map[string]int)

noteFrequency := map[string]float32{
"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
"G0": 24.50, "A0": 27.50, "B0": 30.87,
}


要素の取得

存在しないキーの場合、要素の型のゼロ値が返ってくるだけなので注意。

element := m[key]


要素の追加、更新

m[key] = element


要素の削除

delete(m, key)


要素の存在を確認

要素の取得の際に2つめの値がtrueなら存在、falseなら存在しない。

if element, ok := m[key]; ok {

}


要素数の取得

size := len(m)


関数

引数名の後ろに型名を書く。2つ以上同じ型の引数をとる場合最後以外は型を省略できる。

func add(x, y int) int {

return x + y
}

複数の値を返すことができる。

func swap(x, y string) (string, string) {

return y, x
}

戻り値に名前をつけることができる。

func split(sum int) (x, y int) {

x = sum * 4 / 9
y = sum - x
return x, y
}


無名関数(クロージャ)

変数に代入することも、即実行することも可能。外側の関数で定義されている変数を参照可能(クロージャ)。

f := func(x, y int) int {

return x + y
}

func(ch chan int) {
ch <- ACK
}(replyChan)


defer

関数内でdeferを付けた関数呼び出しを書くと、関数がreturnする直前に実行される。複数のdeferがある場合後のものが先に実行される(LIFO)。

// 関数からreturnする直前にunlock(l)が実行される

func f() {
lock(l)
defer unlock(l)
// ...
}

// 3 2 1 0の順で出力される
func g() {
for i := 0; i <= 3; i++ {
defer fmt.Print(i)
}
}

// この関数は1を返す
func h() (result int) {
defer func() {
result++
}()
return 0
}


メソッド

型に結びついたメソッドを定義することができ、レシーバと呼ばれるその型の引数を持つ関数として表す。メソッドは型を定義したのと同じパッケージ内でしか定義できない。

以下の例ではレシーバをVertex型のvとすることで、Vertex型にAbsメソッドを定義している。

type Vertex struct {

X, Y float64
}

func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}


ポインタレシーバ

レシーバとしてポインタを受け取るようにメソッドを定義することで、メソッド内でレシーバを変更することができる。

type Vertex struct {

X, Y float64
}

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


インタフェース

インタフェースはメソッドをまとめることができる。型がインタフェースの持つメソッドをすべて持っていればその型はインタフェースを実装したことになる。

type Reader interface {

Read(p []byte) (n int, err os.Error)
}

type Writer interface {
Write(p []byte) (n int, err os.Error)
}


埋め込み

インタフェースの定義には、他のインタフェースのメソッドを列挙する代わりにインタフェース名を書いてもいい。これをインタフェースの埋め込みという。以下の例はio.ReadWriterのもので、このインタフェースはio.Readerio.Writerを埋め込んでいる。

type ReadWriter interface {

Reader // io.Reader
Writer // io.Writer
}


空のインタフェース

メソッドを1つも持っていないインタフェースを空のインタフェースinterface{}という。空のインタフェースにはメソッドがないため、すべての型が自動的に空のインタフェースを実装していることになる。そのため、すべての型の値がinterface{}型に代入可能である。

func main() {

var i interface{}
describe(i)

i = 42
describe(i)

i = "hello"
describe(i)
}

func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}


型アサーション

インタフェース型の変数を値の元の型に変換するためには型アサーションを使う。変換できない場合panicが発生する。型アサーションは成功したかどうかのbool値を2つ目に返す。これを受け取ればpanicは発生しない。失敗した場合その型のゼロ値とfalseが返る。

var i interface{} = "hello"

s := i.(string)
fmt.Println(s)

s, ok := i.(string)
fmt.Println(s, ok)

f, ok := i.(float64) // panicは発生しない
fmt.Println(f, ok)

f = i.(float64) // panicが発生
fmt.Println(f)


制御構造


if

if x > max {

x = max
}

条件の前に式を書くことができ、ここで宣言した変数はifブロック内でのみ有効。

if x := f(); x < y {

return x
} else if x > z {
return z
} else {
return y
}


switch

caseに書くのは定数でなくてもよく、,区切りで複数書くこともでき、左のものから評価される。各caseは上のものから評価される。breakがなくても次のcaseに入ることはない。

switch tag {

default:
s3()
case 0, 1, 2, 3:
s1()
case 4, 5, 6, 7:
s2()
}

switch {}switch true {}とみなされる。

switch {

case x < y:
f1()
case x < z:
f2()
case x == 4:
f3()
}

ifと同様条件が評価される前に実行される式を書くことができる。

switch x := f(); {

case x < 0:
return -x
default:
return x
}


fallthrough

case中の最終行にfallthroughを書くことで次のcaseに入るようにできる。

switch x := f(); {

case x%3 == 0:
fmt.Print("Fizz")
fallthrough
case x%5 == 0:
fmt.Print("Buzz")
}


switch

switch x.(type) {}と書くと変数の型で分岐することができる。変数の宣言をすることもでき、その変数の型はcaseに1つだけ書いている場合はその型になる。返り値がinterface{}の関数f()の例を示す。

switch i := f(x).(type) {

case nil:
// iの型はinterface{}
case int:
// iの型はint
case float64:
// iの型はfloat64
case func(int) float64:
// iの型はfunc(int) float64
case bool, string:
// iの型はinterface{}
default:
// iの型はinterface{}
}


for

このiのようにfor変数はfor内でのみ有効。

for i := 0; i < 10; i++ {

f(i)
}


while

セミコロンを省略するとfor ; a < b; {}と書いたのと同じになり、whileのように使える。

for a < b {

a *= 2
}


無限ループ

条件も省略するとfor true {}とみなされ、無限ループにできる。

for {

}


break

for i := 0; i < 100; i++ {

if f(i) == -1 {
break
}
fmt.Println(i)
}


continue

次の繰り返しに進む。

for i := 0; i < 100; i++ {

if f(i) == 0 {
continue
}
fmt.Println(i)
}


range

配列、スライス、文字列、マップ、チャネルの全要素に対しループする。


1つめの変数
2つめの変数

配列、スライス
インデックス
インデックスに対応する要素

文字列
バイト単位でのインデックス
文字(ルーン)

マップ
キー

チャネル
送信された値


配列・スライス

a := [3]int{2, 4, 6}

for i, v := range a {
fmt.Println(i, v)
}

s := []string{"あ", "い", "う"}

for i, v := range s {
fmt.Println(i, v)
}


文字列

Unicodeコードポイント(rune)単位で取り出されるが、インデックスはバイト単位。

for i, v := range "寿司🍣Beer🍺" {

fmt.Println(i, string(v))
}

0 寿

3 司
6 🍣
10 B
11 e
12 e
13 r
14 🍺


マップ

取り出される順番は保証されない。

m := map[string]int{"mon": 0, "tue": 1, "wed": 2, "thu": 3, "fri": 4, "sat": 5, "sun": 6}

for key, val := range m {
fmt.Println(key, val)
}

fri 4

sat 5
sun 6
mon 0
tue 1
wed 2
thu 3


channel

channelcloseされるまで送信された値についてループする。

var ch chan Work = producer()

for w := range ch {
doWork(w)
}

変数へ代入しないでcloseされるまで待つ

for range ch {

}


並行処理


goroutine

関数、メソッドの呼び出し時にgoを付けることで新たなgoroutineが実行される。

go list.Sort() // 並行して実行、終了を待たない

無名関数が使われることが多い。クロージャのため外側の関数の変数を参照できる。

func Announce(message string, delay int64) {

go func() {
time.Sleep(delay)
fmt.Println(message)
}()
}


channel

channelによって値の受け渡しと同期実行を行うことができる。make関数で作成するが、オプションでバッファサイズを整数で指定できる。指定しなかった場合は0となり、同期的な動作をできる。

ci := make(chan int)           // 整数型のバッファなしチャネル

cj := make(chan int, 0) // 整数型のバッファなしチャネル
cs := make(chan *os.File, 100) // Fileへのポインタ型のバッファありチャネル

値の送信、受信には<-を使う。channelにバッファがない場合、送信側と受信側の準備ができるまで送信はできない。バッファがある場合、送信側はバッファが一杯になるまでは相手を待たずに送信でき、受信側はバッファが空になるまでは受信しつづけられる。channelはキューのように動作する(FIFO)。また、channelに対してlenでバッファにある要素数、capでバッファサイズを取得できる。

ch <- v   // vをチャネルchへ送信する

v := <-ch // ch から受信した変数をvへ割り当てる

c := make(chan int)

go func() {
list.Sort()
c <- 1 // 終わったことを知らせるためになんでもいいから送信する
}()
doSomethingForAWhile()
<-c // 何か送信されるまで待つ(値は捨てる)


close

送信側は送信する値がもうないことを示すためにcloseできる。ただし、これは受信側がもう送信されてこないか知る必要がある場合のみ使う。ファイルと異なりchannelは通常closeする必要はない。

close()

受信側は受信時の2つ目の変数がfalseかどうかでcloseされたかが分かる。

v, ok := <-ch

また、rangeで受信していた場合、closeされるとループから抜ける。

func fibonacci(n int, c chan int) {

x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}

func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}


select

複数の通信操作の中から実行可能なものを選択する。caseのうち実行可能となったものが選ばれ実行される。複数実行可能なcaseがある場合、ランダムに選択される。実行可能なものがない場合、defaultがあれば実行されるが、なければどれかが実行可能になるまでそこで停止する。

var c, c1, c2, c3 chan int

var i1, i2 int

select {
case i1 = <-c1: // c1で受信できた場合
case c2 <- i2: // c2にi2を送信できた場合
case i3, ok := <-c3:
if ok {
// c3で受信できた場合
} else {
// c3がcloseされた場合
}
default:
// どのcaseも実行できなかった場合
}

// ランダムに1か0をcに送信し続ける
for {
select {
case c <- 0:
case c <- 1:
}
}


エラー

プログラムのエラーの状態をerror型の変数で表現する。error型はErrorメソッドだけを持つインタフェースとして定義されている。

type error interface {

Error() string
}

関数がerror型を返す場合、nilかどうかでエラーが起きたかを判別できる。

i, err := strconv.Atoi("42")

if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)


panic

panicが発生した場合、その関数の実行は停止されプログラムは終了する。この際deferに指定された関数は実行される。

panic関数を使いpanicを自分で発生させることができる。

var user = os.Getenv("USER")

func init() {
if user == "" {
panic("no value for $USER")
}
}


recover

deferで指定した関数内でrecover関数を呼びだすとプログラムの終了を防ぐことができる。recover関数はpanicに渡された引数を返す。

func server(workChan <-chan *Work) {

for work := range workChan {
go safelyDo(work)
}
}

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