Golang 入門

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 型のパラメータ xy を受け取り、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
}