定義と戻り値
関数はfuncキーワードで定義します。名前、引数、戻り値の型を書き、returnで値を返します。続く引数が同じ型のとき、型を1回だけ書いてa, b intと省略できます。
func add(a, b int) int {
return a + b
}
func mix(a, b int, factor float64) float64 {
return float64(a+b) * factor
}複数戻り値とerror
戻り値の型を(T1, T2)のように並べると、複数の値を返せます。代入で受け取るときは、左辺の個数を戻り値の個数に合わせます。
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を書くと、登録は繰り返されますが実行は関数終了まで遅延します。
for i := 0; i < 3; i++ {
defer fmt.Println(i) // 登録時にiが評価される → 終了時に2, 1, 0の順
}名前付き戻り値
戻り値の型の前に名前を付けると、その名前が関数内の変数として使えます。関数に入った時点で、各戻り値は型のゼロ値です。
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // この時点のx, yを返す(return x, yと同じ)
}return文に値を指定した場合、まず名前付き戻り値の変数にその値が代入され、そのあとでdeferが実行されます。そのため、deferの中で名前付き戻り値を書き換えると、最終的に呼び出し元へ渡る値が変わります。
func scaled() (result int) {
defer func() {
result *= 7
}()
return 6 // 呼び出し側には42が渡る
}可変長引数
末尾の引数を...Tと宣言すると可変長引数になり、呼び出し側は0個以上の値を渡せます。関数内では型[]Tのスライスとして扱われます。引数を1つも渡さなかった場合、関数内のスライスはnil(長さ0、容量0)になります。
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
sum()
sum(1, 2, 3)スライスを渡すときは、末尾に...を付けます。新しいスライスは作られず、呼び出し元のスライスがそのまま...Tの引数になります。変数でもリテラルでもよく、sum(origin...)やsum([]int{4, 5}...)のように書けます。
...Tと宣言された引数に対して個別の値とスライス展開を同時に渡すこと(sum(1, s...))はできません。通常の引数のあとに可変長引数がある関数では、通常の引数とs...を並べられます。
func greet(prefix string, names ...string) {}
greet("hi:", []string{"Ann", "Bo"}...)値を個別に列挙して渡す場合と、スライスを...で渡す場合では、メモリ上の扱いに違いがあります。個別の値を列挙すると、呼び出し時に新しいスライスと、それが参照する配列が割り当てられ、引数の値がそこへコピーされます。...付きで渡すと配列は共有されるため、関数内で要素を書き換えると呼び出し元のスライスにも反映されます。
func replaceFirst(nums ...int) {
if len(nums) > 0 {
nums[0] = 0
}
}
origin := []int{10, 20, 30}
replaceFirst(origin...)
fmt.Println(origin) // [0 20 30] 元のスライスが参照している配列が書き換わる関数値
関数は第一級の値です。関数型の変数に代入して、別名から呼び出せます。
func twice(n int) int {
return n * 2
}
var fn func(int) int = twice
z := fn(3) // zは6関数リテラル
funcに名前を付けず書いた式が関数リテラルです。無名関数ともいいます。
add := func(x, y int) int {
return x + y
}
total := add(1, 2) // 3関数リテラルの直後に関数を実行する()を付けると、変数に代入せずその場で呼び出せます。
n := func(x int) int { return x * 2 }(3) // 6関数リテラルはクロージャです。外側の関数で宣言した変数を参照でき、その変数はリテラルと共有され、リテラルが参照可能な間は保持されます。次の例でクロージャなのは、counterが返す内側の関数です。この関数は外側のnを参照します。next()を呼ぶたびに同じnが更新され、1, 2と増えます。
func counter() func() int {
n := 0
return func() int { // この関数リテラルがクロージャ
n++
return n
}
}
next := counter()
v1 := next() // 1
v2 := next() // 2メソッドの定義
メソッドは型に紐づく関数です。メソッドにはレシーバという特殊な引数が必要で、どの型の値に対して実行するかを指定します。
type Rect struct{ W, H float64 }
func (r Rect) Area() float64 {
return r.W * r.H
}型に紐づくため、オブジェクト指向な言語とは異なり、構造体以外の型にもメソッドを定義することができます。
type Length float64
func (l Length) Double() Length {
return l * 2
}同じ型のメソッド集合内でレシーバ名は一意である必要があります。
値レシーバとポインタレシーバ
レシーバには値レシーバとポインタレシーバの二つがあります。値レシーバは、呼び出し元の値のコピーを受け取って実行するため、元の値に副作用がありません。ポインタレシーバは、呼び出し元の値へのポインタを受け取ってから実行するため、元の値を変更することができます。
func (r Rect) Area() float64 {
return r.W * r.H
}
// 呼び出し元の値を書き換えている
func (r *Rect) Scale(f float64) {
r.W *= f
r.H *= f
}値レシーバで定義されたメソッドは、値(Rect型)からしか呼び出すことができません。同様にポインタレシーバで定義されたメソッドは、ポインタ(*Rect型)からしか呼び出すことができません。しかし、コンパイル時に自動補完が走るため、どちらのケースでも変数から呼び出す形で問題なく動きます。
type Rect struct{ W, H float64 }
func (r Rect) Area() float64 { return r.W * r.H }
func (r *Rect) Scale(f float64) { r.W *= f; r.H *= f }
r := Rect{W: 3, H: 4}
r.Scale(2) // コンパイラが (&r).Scale(2) に読み替える
fmt.Println(r.Area())
p := &Rect{W: 3, H: 4}
p.Scale(2) // ポインタレシーバなのでそのまま呼べる
fmt.Println(p.Area()) // コンパイラが (*p).Area() に読み替えるただし、リテラル・関数の戻り値・mapのインデックス式など、アドレスが取得できない値の場合は自動補完が効かず、ポインタレシーバのメソッドを直接呼び出すことができません。
// Rect{W: 1, H: 1}.Scale(2) // NG: リテラルはアドレスが取得できない
(&Rect{W: 1, H: 1}).Scale(2) // OK: &Rect{...}でリテラルから*Rectを作れる(変数に入れないので変更は捨てられる)
rects := map[string]Rect{"a": {}}
// rects["a"].Scale(2) // NG: mapのインデックス式はアドレスが取得できない
v := rects["a"]
v.Scale(2)
rects["a"] = v // 変数に取り出して変更し、mapへ戻すポインタレシーバのメソッドは、レシーバがnilであっても呼び出し自体は可能です。ただし、メソッド内でフィールドにアクセスした時点で nilポインタデリファレンスが発生しパニックになります。一方、値レシーバのメソッドをnilのポインタから呼び出すと、コンパイラが値をコピーしようとして(*p)の展開を行うため、呼び出しの時点でnilポインタデリファレンスが発生しパニックになります。
var p *Rect
// p.Area() // 値レシーバ: 呼び出し時に(*p)で値コピー → nilポインタデリファレンスでパニック
// p.Scale(2) // ポインタレシーバ: 呼び出しは成功するが、内部でr.Wを参照 → nilポインタデリファレンスでパニックメソッド式とメソッド値
メソッドは関数値として扱えます。
r := Rect{W: 3, H: 4}
fn := r.Area // メソッド値: レシーバがあらかじめ結び付いている
fmt.Println(fn())
area := Rect.Area // メソッド式: レシーバを第1引数で渡す
fmt.Println(area(r))埋め込みとメソッド昇格
構造体に別の型を埋め込むと、その型のメソッドが昇格して外側の型から呼び出せるようになります。外側の型に同名メソッドがある場合は引数が異なる場合でも外側が優先されます。
type Engine struct{}
func (e Engine) Start() {}
type Car struct {
Engine
}
var c Car
c.Start() // Engine.Start が昇格