Menu

Go言語の関数:宣言、呼び出し、関数を値として渡す

Goで関数を宣言して呼び出す方法を解説します。引数、型をまとめた引数、戻り値、値渡し、そして保存したり受け渡したりできる値としての関数まで。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

関数を宣言する

関数宣言は、func、名前、引数、結果の型、本体からなります。型は引数名の前ではなく後ろに書きます。

add は2つの int を受け取り、int を返します。greet は何も返さないので、シグネチャに結果の型がなく、本体に return も要りません。

結果を宣言した関数は、終端文で終わらなければなりません。returnせずに閉じ波かっこに到達する経路があると、コンパイラは missing return で止まります。

型をまとめた引数

同じ型の引数が続くときは、型名を1つにまとめられます。func add(a, b int) intfunc add(a int, b int) int とまったく同じ意味です。混在させることもできます。

func scale(x, y float64, factor int) (float64, float64)

ここでは xyfloat64factorint です。

戻り値

関数は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)はそのパッケージの中からしか見えません。publicprivate のキーワードはなく、大文字小文字がルールのすべてです。

よくある間違い

  • 引数の変更が呼び出し側に届くと思う。 引数への代入はコピーを変えるだけです。新しい値を返すか、ポインタを受け取ります。
  • 関数内でappendして結果を無視する。 func add(s []int) { s = append(s, 1) } は呼び出し側のスライスの長さに何の影響も与えません。func add(s []int) []int と書き、s = add(s) として使います。
  • 使われない結果。 Goは戻り値を無視することを許します。go vet は捨てられたエラーを報告しませんが、errcheckgolangci-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は関数のオーバーロードやデフォルト引数をサポートしていますか?

していません。パッケージ内の関数名はそれぞれ一意でなければならず、すべての引数を毎回の呼び出しで渡す必要があります。よく使われる代替手段は、別々の名前(NewServerNewServerWithTLS)、設定用の構造体、可変長引数、またはfunctional optionsです。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める