Go 関数の定义と呼び出し
関数(Function)を使用すると、特定のタスクを実行するコードをカプセル化(Encapsulation)でき、コードをモジュール化し、再利用可能で理解しやすいものにすることができます。
本章では、Go言語における関数の定義と呼び出し方法について、関数シグネチャ、パラメータの受け渡し、戻り値の核心的なコンセプトを含めて包括的に解説します。
1. 関数の定義
Go言語では、func キーワードを使用して関数を定義します。その後に関数名、パラメータリスト(丸括弧で囲む)、任意の戻り値リストが続き、最後にコードブロック(波括弧で囲む)を記述します。
1.1 基本構文の解説
関数を定義する際の一般的な構文は以下の通りです。
func 関数名(パラメータ1 型1, パラメータ2 型2, ...) (戻り値の型1, 戻り値の型2, ...) {
// 関数本体のコード
return 戻り値1, 戻り値2, ...
}各構成要素の解説:
funcキーワード: 関数を定義することをコンパイラに明示します。- 関数名: 関数に付ける名前です。その関数が何をするのかが一目でわかる、説明的な名前(Descriptive name)を使用すべきです。
- パラメータリスト: 関数が受け取る入力データです。各パラメータは名前と型で構成されます。
- 戻り値リスト: 関数が実行を終えた後に出力するデータの型です。何も返さない場合は省略可能です。
{ ... }: 関数本体。具体的な実行ロジックが含まれます。returnステートメント: 結果を関数の外に送り出します。戻り値が定義されている場合は、必ずreturnが必要です。
1.2 シンプルな関数の例
2つの整数の和を計算するシンプルな関数を見てみましょう。
package main
import "fmt"
// add という名前の関数を定義
func add(x int, y int) int {
sum := x + y
return sum // 計算結果を返す
}
func main() {
result := add(5, 3) // add 関数を呼び出す
fmt.Println(result) // 出力: 8
}この例では、add 関数は2つの int 型のパラメータ x と y を受け取り、int 型の値を返すことを宣言しています。
1.3 パラメータ型の短縮記法 (Shorthand)
関数に同じ型のパラメータが連続して続く場合、最後のパラメータにだけ型を記述し、それ以前の型を省略することができます。
// x と y は両方とも int 型なので、x, y int と短縮できる
func add(x, y int) int {
return x + y
}この記法により、コードをよりシンプルで洗練されたものにできます。
1.4 引数のない関数
関数はパラメータを受け取らない場合もあります。その際は、丸括弧を空にします。
package main
import "fmt"
func greet() {
fmt.Println("Hello, world!")
}
func main() {
greet() // 呼び出し時も括弧を省略することはできません
}2. Go言語の真骨頂:多値戻り値
Go言語は、一つの関数から同時に複数の値を返すことをネイティブにサポートしています。これは非常に強力な機能であり、最も典型的なユースケースは、処理結果とエラーメッセージ(Error)を同時に返す場合です。
package main
import (
"fmt"
"errors"
)
// divide 関数は2つの値を返す:計算結果 (float64) と エラー情報 (error)
func divide(x, y float64) (float64, error) {
if y == 0 {
// 除数が 0 の場合、0 と具体的なエラーを返す
return 0, errors.New("除数はゼロにできません")
}
// 計算成功時、結果と nil (エラーがないことを示す) を返す
return x / y, nil
}
func main() {
// 正常な除算
result, err := divide(10.0, 2.0)
if err != nil {
fmt.Println("エラーが発生しました:", err)
return
}
fmt.Println("計算結果:", result) // 出力: 計算結果: 5
// ゼロ除算エラーをトリガー
result, err = divide(10.0, 0.0)
if err != nil {
fmt.Println("エラーが発生しました:", err) // 出力: エラーが発生しました: 除数はゼロにできません
return
}
fmt.Println("計算結果:", result)
}この例では、受け取り側は正常な戻り値と発生する可能性のあるエラーを同時に処理する必要があり、これにより開発者はより安全な防御的コード(Defensive code)を記述することが強制されます。
3. 名前付き戻り値
Goでは、関数シグネチャの中で戻り値に直接名前を付けることも可能です。名前を付けると、これらの変数は関数の実行開始時に自動的にゼロ値(Zero value)で初期化されます。
名前付き戻り値を使用する場合、関数の末尾で引数を持たない return(裸のreturn / Naked return)を使用できます。これにより、現在の名前付き変数の値が自動的に返されます。
package main
import "fmt"
// sum と difference は名前付き戻り値
func addAndSubtract(x, y int) (sum int, difference int) {
sum = x + y // 名前付き戻り値の変数に直接代入
difference = x - y
return // Naked return:sum と difference の現在の値を自動的に返す
}
func main() {
s, d := addAndSubtract(5, 3)
fmt.Println("和:", s, "差:", d) // 出力: 和: 8 差: 2
}ベストプラクティス: Naked returnは非常に短い関数では便利ですが、長い関数で使用すると可読性が著しく低下します(何を返しているのかを一目で判断するのが難しくなるため)。そのため、Naked returnと名前付き戻り値の使用は、非常に短い関数に留めることを推奨します。
4. 関数の呼び出しと値渡し
関数の呼び出しは非常に簡単で、関数名に続けて対応する引数を渡すだけです:multiply(4, 6)。
ただし、Go言語における極めて重要な内部原理を覚えておく必要があります。Goの関数パラメータの受け渡しは、すべて「値渡し」(Pass by Value)です。
これは、変数を関数に渡すとき、Goは実際にはその変数の値をコピーして、そのコピーを関数に渡すことを意味します。関数内部でそのコピーをどのように変更しても、関数の外にある元の変数には一切影響を与えません。
package main
import "fmt"
func modifyValue(x int) {
x = x * 2 // ここで変更しているのは x のコピー
fmt.Println("関数内部の値:", x) // 出力: 20
}
func main() {
value := 10
modifyValue(value) // value のコピーを渡す
// 元の値は全く影響を受けない
fmt.Println("関数外部の値:", value) // 出力: 10
}もし関数に外部変数の値を直接変更させたい場合は、ポインタ(Pointers)を使用する必要があります。これについては後の章で詳しく解説します。
4.1 不要な戻り値の破棄
関数が複数の値を返すが、そのうちの特定の値にしか興味がない場合、Goのコンパイラは「宣言した変数を使用しない」ことを許可しません。このような場合、ブランク識別子 _(アンダースコア)を使用して、不要な戻り値を破棄します。
package main
import "fmt"
func getCoordinates() (int, int, int) {
return 10, 20, 30
}
func main() {
// 最初の2つの座標だけが必要で、3つ目は破棄する
x, y, _ := getCoordinates()
fmt.Println("X:", x, "Y:", y) // 出力: X: 10 Y: 20
}