定義と戻り値
関数はfuncキーワードで定義します。名前、引数、戻り値の型を書き、returnで値を返します。続く引数が同じ型のとき、型を1回だけ書いてa, b intと省略できます。a int, b intと書いても同じ意味です。型が混ざるときは同型が連続する部分だけ省略し、それ以外はそれぞれ型を書きます。
func add(a, b int) int { // a int, b intと同じ
return a + b
}
func mix(a, b int, factor float64) float64 { // a int, b int, factor float64
return float64(a+b) * factor
}
// mix(1, 2, 1.5) → 4.5複数戻り値とerror
戻り値の型を(T1, T2)のように並べると、複数の値を返せます。呼び出し側はv, err := f()のように左辺の変数の数だけ受け取ります。
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
v, err := divide(10, 2)
if err != nil {
return err
}defer
deferは関数がreturnするとき(正常終了・panicいずれも)に実行する処理を登録する文です。分岐やループの制御ではなく、関数スコープで後片付けを書くための構文です。
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()複数のdeferは後入れ先出しで実行されます。引数はdefer登録時に評価され、関数終了時ではありません。ループ内でdeferを書くと、登録は繰り返されますが実行は関数終了時まで遅延します。
for i := 0; i < 3; i++ {
defer fmt.Println(i) // 登録時のiをキャプチャ → 終了時に2, 1, 0の順
}if初期化文との組み合わせ
呼び出しとerrのチェックを1行にまとめるときにif初期化文を使います。
if _, err := strconv.Atoi(input); err != nil {
return err // inputが整数でなければ終了
}名前付き戻り値
戻り値の型の前に名前を付けると、その名前が関数内の変数として使えます。
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // この時点のx, yを返す(return x, yと同じ)
}戻り値に名前を付けているとき、値を書かないreturnはnaked returnと呼ばれ、名前付き戻り値をそのまま返します。名前付き戻り値はドキュメントとして有用ですが、長い関数では何を返しているか追いにくくなります。defer内で名前付き戻り値を書き換えるとreturnの結果が変わるため、意図しない場合があります。
可変長引数
末尾の引数を...Tと書くと可変長引数になります。呼び出し側は値を列挙するか、スライスを...付きで渡します。
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
sum(1, 2, 3)
sum([]int{4, 5}...)値渡しと関数値
関数は第一級の値です。引数は値のコピーであり、呼び出し側の変数は関数内で引数を書き換えても変わりません。関数型の変数に代入して、別名から呼び出せます。
func double(n int) int {
m := n
n = 0 // コピーnを書き換えても呼び出し側には及ばない
return m * 2
}
x := 10
y := double(x) // yは20
// xは10のまま
var fn func(int) int = double
z := fn(3) // zは6関数リテラル
funcに名前を付けず書いた式が関数リテラル(無名関数)です。第一級の値なので、変数に代入してから呼び出せます。
add := func(x, y int) int {
return x + y
}
sum := add(1, 2) // 3即時呼び出し
関数リテラルの直後に(引数…)を付けると、変数に代入せずその場で呼び出せます。
n := func(x int) int { return x * 2 }(3) // 6クロージャ
関数リテラルは外側の関数で宣言した変数を参照できます。参照した変数はリテラルと共有され、リテラルが参照可能な間は保持されます。
func counter() func() int {
n := 0
return func() int {
n++
return n
}
}
next := counter()
v1 := next() // 1
v2 := next() // 2