関数を宣言する
関数宣言は、func、名前、引数、結果の型、本体からなります。型は引数名の前ではなく後ろに書きます。
add は2つの int を受け取り、int を返します。greet は何も返さないので、シグネチャに結果の型がなく、本体に return も要りません。
結果を宣言した関数は、終端文で終わらなければなりません。returnせずに閉じ波かっこに到達する経路があると、コンパイラは missing return で止まります。
型をまとめた引数
同じ型の引数が続くときは、型名を1つにまとめられます。func add(a, b int) int は func add(a int, b int) int とまったく同じ意味です。混在させることもできます。
func scale(x, y float64, factor int) (float64, float64)
ここでは x と y が float64、factor が int です。
戻り値
関数は0個、1個、または複数の値を返せます。複数の結果はかっこの中に書き、呼び出し側は多重代入で受け取ります。
これは 3 2 と表示します。Goでこの機能が最もよく使われるのは (value, error) のペアで、複数の戻り値のページで専用に扱っています。
引数はコピーされる
Goはすべての引数を値で渡します。関数はコピーを扱うので、引数に代入しても呼び出し側の変数は決して変わりません。
出力は次のとおりです。
{1 2}
{100 2}
[99 2 3]
3行目に驚く人が多いです。スライスの値は小さなヘッダー(ポインタ、長さ、容量)です。コピーは同じ元の配列を指すので、s[0] への書き込みは呼び出し側の要素に書き込みます。ただし関数内の append は新しい配列を確保することがあり、呼び出し側はその新しいヘッダーを見ることはありません。関数がスライスを伸ばすなら、新しいスライスを返します。マップにも同じ理屈が当てはまります。マップの値は共有データを参照しているので、関数が追加したキーは呼び出し側から見えます。
関数が呼び出し側の変数を変更する必要があるときや、呼び出しのたびにコピーしたくない大きな構造体のときは、ポインタの引数を使います。
関数は値
関数には型があり、名前を除いて func(int, int) int のように書きます。関数を変数に保存したり、マップやスライスに入れたり、別の関数に渡したりできます。
square は変数に代入した無名関数です。無名関数は周りのスコープの変数を読んだり変更したりもでき、それがクロージャになります。
同じ形がいろいろな場所に出てくるときは、名前付きの関数型を使うとシグネチャが読みやすくなります。
type Transform func(int) int
func apply(xs []int, f Transform) []int
関数型のゼロ値は nil です。nilの関数を呼ぶと invalid memory address or nil pointer dereference でpanicするので、省略可能なコールバックは呼ぶ前にチェックします。関数の値は nil とだけ比較でき、関数どうしは比較できません。
再帰
関数は自分自身を呼べます。Goに末尾呼び出しの最適化はありませんが、ゴルーチンのスタックは必要に応じて伸びるので、普通の深さの再帰なら問題ありません。
for i := range 6 は0から5まで数え、Go 1.22以降が必要です。
Goの関数にないもの
- オーバーロードはない。 引数が違っても、1つのパッケージで2つの関数が同じ名前を持つことはできません。
- デフォルト引数も名前付き引数もない。 すべての引数を毎回の呼び出しで、順番どおりに渡します。設定用の構造体を使えば、名前付きで省略可能なフィールドが手に入ります:
NewServer(Config{Port: 8080})。 - 名前付きの関数をネストできない。 関数の本体の中で宣言できるのは無名関数だけで、たいていは変数に代入します。
可変長の引数リストは可変長引数関数でサポートされています。
公開関数と非公開関数
名前が大文字で始まる関数(Parse)は公開され、他のパッケージから呼べます。小文字の名前(parse)はそのパッケージの中からしか見えません。public や private のキーワードはなく、大文字小文字がルールのすべてです。
よくある間違い
- 引数の変更が呼び出し側に届くと思う。 引数への代入はコピーを変えるだけです。新しい値を返すか、ポインタを受け取ります。
- 関数内でappendして結果を無視する。
func add(s []int) { s = append(s, 1) }は呼び出し側のスライスの長さに何の影響も与えません。func add(s []int) []intと書き、s = add(s)として使います。 - 使われない結果。 Goは戻り値を無視することを許します。
go vetは捨てられたエラーを報告しませんが、errcheck(golangci-lintの一部)などのリンターは報告します。_ =は、無視すると決めたときにだけ使います。
よくある質問
Goで関数はどう定義しますか?
func キーワード、名前、名前の後に型を書いた引数リスト、結果の型を使います:func add(a, b int) int { return a + b }。結果のない関数は型を省略し、複数の結果を持つ関数はかっこの中に並べます:func divmod(a, b int) (int, int)。
Goは値渡しですか、参照渡しですか?
常に値渡しです。関数は各引数のコピーを受け取ります。int や構造体なら、そのコピーは呼び出し側の変数とは独立しています。スライス、マップ、チャネル、ポインタは共有データを参照する小さな値なので、それらが指す要素への変更は呼び出し側から見えますが、引数そのものへの再代入は見えません。関数が呼び出し側の変数を変更する必要があるなら、ポインタ(*T)を渡します。
Goで関数を引数として渡せますか?
渡せます。関数は func(int) int のような型を持つ値です。その型の引数を宣言し、シグネチャが一致する関数なら名前付きでも無名でも渡せます:func apply(xs []int, f func(int) int)。
Goは関数のオーバーロードやデフォルト引数をサポートしていますか?
していません。パッケージ内の関数名はそれぞれ一意でなければならず、すべての引数を毎回の呼び出しで渡す必要があります。よく使われる代替手段は、別々の名前(NewServer、NewServerWithTLS)、設定用の構造体、可変長引数、またはfunctional optionsです。