はじめに
Go言語の文法についてまとめました。
参考
訳のほうはだいぶ古くなっていて内容が追加されたりしたものが反映されていないので、できれば原文を読んだ方がいいと思います。
- A Tour of Go
- A Tour of Go 日本語
- Effective Go
- 実践Go言語(Effective Goの日本語訳)
- The Go Programming Language Specification
- Goプログラミング言語仕様(The Go Programming Language Specificationの日本語訳)
プログラムの実行
プログラムの実行は
-
main
パッケージの初期化 - パッケージ中の
main
関数の呼び出し
の順で行われ、main
関数からreturn
した時点で終了する。
main
関数は引数、返り値を持たない。
func main() {
}
パッケージの初期化
パッケージの初期化は
- インポートするパッケージの初期化
- パッケージ変数への初期値の代入
-
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
となる。
-
*
/
%
<<
>>
&
&^
-
+
-
|
^
-
==
!=
<
<=
>
>=
&&
||
変数
値を入れずに初期化した場合、その型のゼロ値が入る。関数内で宣言したのに使っていない変数があるとコンパイル時エラーになるので注意。
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]
省略形式
関数の中かif
、for
、switch
で変数を宣言するときに使うことができる。
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"
[]byte
→string
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ø"
[]rune
→string
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.Reader
とbufio.Writer
のポインタを埋め込んでいる。bufio.Reader
はio.Reader
、bufio.Writer
はio.Writer
をそれぞれ実装している。この埋め込みによりbufio.ReadWriter
はRead
メソッドとWrite
メソッドを持つことになるため、io.Reader
、io.Writer
、io.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つの情報から構成されている。長さは要素数で、キャパシティはスライスの最初の要素から数えたときの参照先の配列の要素数。長さ、キャパシティはそれぞれlen
、cap
関数で取得することができる。
スライスは配列・配列へのポインタ・スライスから作ることができる。s[low:high]
でs
のインデックスがlow
からhigh - 1
までの要素を持つスライスができる。low
を省略すると0
、high
を省略すると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.Reader
とio.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
channel
がclose
されるまで送信された値についてループする。
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)
}