Go言語のfuncで関数を定義する方法や複数の戻り値についてわかりやすく解説

スポンサーリンク
スポンサーリンク
amazon
スマイルSALE
--:--:--
ad. 価格範囲を指定して商品を探せます

funcの概要

関数を定義するGoの予約語

func

概要 funcは、特定の処理(サブルーチン)をカプセル化する「関数」や、型に紐づく「メソッド」、さらには実体を持たない「匿名関数(クロージャ)」を静的に定義・宣言するために使用します。

  • Go言語における手続き型・関数型プログラミングの最小コンポーネントであり、すべての実行コードは必ずいずれかの func 内部に属さなければならない。
  • 他言語に類を見ない「マルチバリュー・リターン(複数の独立した戻り値)」を標準でサポートし、エラーハンドリング(value, err パターン)の基盤となる。
  • 関数そのものが「第一級オブジェクト(First-class object)」として扱われるため、変数への代入、他の関数への引数受け渡し、戻り値としての関数返却が可能。

funcの考え方をイメージで理解

基本的なfuncの使い方(サブルーチンの定義と値の引き渡し)

入力を受け取り、定義された手続きに沿って命令をシーケンシャルに処理する最も標準的な関数の基本形です。

package main

import "fmt"

// 挨拶を出力する関数。引数「name」の型を固定
func greet(name string) {
    fmt.Println("こんにちは、", name, "さん!")
}

func main() {
    greet("太郎") // 定義した関数に実引数(アーギュメント)を渡して呼び出し
}

構造解説:

  • func greet(name string) のように、Goでは引数の「変数名」の「後ろ」にデータ型を記述します。
  • この関数は戻り値を持たない(省略されている)ため、内部の fmt.Println を実行し終えた時点で自動的に呼び出し元(main)へ制御を差し戻します。

実行結果:

こんにちは、 太郎 さん!

単一の戻り値を持つ関数(評価値のファクトリ)

処理結果を呼び出し元に返却して別の演算に再利用させる場合、引数リストの右側に戻り値のデータ型を明記します。

package main

import "fmt"

// 2つの整数を加算し、結果のint型を戻り値として宣言
func add(a int, b int) int {
    return a + b // returnキーワードにより値を返却し関数を抜ける
}

func main() {
    result := add(3, 5) // 戻り値を受け取って変数へ代入
    fmt.Println("加算結果:", result)
}

構造解説:

  • func add(a int, b int) int の末尾の int が戻り値の型を表します。なお、隣接する引数が同じ型であれば func add(a, b int) int と省略表記することも可能です。
  • 型が定義されている関数では、実行フローの末尾に必ず型に適合した return 命令を配置しなければコンパイルエラーになります。

実行結果:

加算結果: 8

複数の戻り値を持つ関数(マルチバリュー・リターン)

Go言語の最大の強みの1つです。構造体や配列に値を詰め直すことなく、複数の異なる結果を同時にカンマ区切りで返却できます。

package main

import "fmt"

// 割り算の「商」と「余り」を同時に計算して2つのint値を返却
func divide(a int, b int) (int, int) {
    return a / b, a % b
}

func main() {
    // 2つの戻り値を同時に多重代入で受け取る
    quotient, remainder := divide(10, 3)
    fmt.Println("商:", quotient, "余り:", remainder)
}

構造解説:

  • 戻り値が複数ある場合は、型を括弧で括って (int, int) のように宣言します。
  • この仕組みにより、Go言語では「1番目に本来のデータ、2番目にエラーオブジェクト」を返す func process() (Result, error) 形式の例外代替ハンドリングが徹底されています。

実行結果:

商: 3 余り: 1

可変長引数(Variadic Parameter)を受け取る柔軟な関数定義

スライスではなく、任意の個数の引数をシームレスに受け入れたい場合、型の前に ...(スリードット)を付与することで、内部的には配列として安全にデータを処理できます。

package main

import "fmt"

// 任意の個数の整数を受け取って合計を算出する関数
func sum(nums ...int) int {
    total := 0
    // 内部では nums は「[]int」型(スライス)として扱われる
    for _, n := range nums {
        total += n
    }
    return total
}

func main() {
    fmt.Println("2つの合計:", sum(10, 20))
    fmt.Println("4つの合計:", sum(1, 2, 3, 4))
}

構造解説:

  • nums ...int と定義することで、引数ゼロ個から無限個(メモリの許す限り)までの値を直接カンマ区切りで入力させることができ、柔軟な汎用ユーティリティ(fmt.Printf 等と同様の構造)を自作できます。

実行結果:

2つの合計: 30
4つの合計: 10

匿名関数(Anonymous Function)とファーストクラス・ファンクション

func に名前をつけずにその場でインライン展開して変数に格納したり、非同期処理(ゴルーチン)の即時実行トリガーとして起動したりできます。

package main

import "fmt"

func main() {
    // 匿名関数を変数「multiply」へ直接代入
    multiply := func(x, y int) int {
        return x * y
    }

    // 変数経由で関数オブジェクトを実行
    result := multiply(4, 5)
    fmt.Println("乗算結果:", result)
}

構造解説:

  • 関数が名前を持たないため、スコープを汚さずに局所的なラムダ式やコールバック関数を動的に構築する際に重宝します。外部の変数を閉じ込める「クロージャ(Closure)」としての性質も併せ持ちます。

実行結果:

乗算結果: 20

学術的・設計的注意点

  • 関数名先頭の大小文字によるアクセスコントロール(Public / Private): Go言語では、パッケージ外部に対する関数の公開・非公開を制御するための修飾子(public などのキーワード)が存在しません。代わりに「関数名の先頭文字が大き文字か小文字か」というシンタックスにその決定権が委ねられています。func Calculate() のように大文字から始めればパッケージ外から参照できる「エクスポート(公開)」状態になり、func calculate() のように小文字から始めると該当パッケージ専用の「インナークラス(非公開)」状態に厳格にカプセル化されます。
  • 名前付き戻り値(Named Return 値)とベアリターンの危険性: func eval() (result int) のように、戻り値の型定義部分に変数の識別子(名前)を事前に割り当てておくことが可能です。この場合、関数内で result = 100 と代入した後に、引数を持たない return(ベアリターン)と書くだけで result の中身が自動的に返却されます。しかし、この手法は関数が数行を超える中規模以上のロジックになると、「最終的に何が返却されるのか」が追跡しづらくなり、可読性を大幅に低下させる副作用があるため、使用はごく短い関数に限定すべきと設計上推奨されています。

よくある質問(FAQ)

Q: 引数に渡したポインタ型の変数を、funcの内部で書き換えるとどうなりますか?
A: Go言語の引数引き渡し仕様はすべて「値渡し(値のコピー)」です。しかし、ポインタ(*int 等)を引数として渡した場合、コピーされるのは「メモリのアドレス値そのもの」です。したがって、関数内部で *ptr = 99 のようにデリファレンスをかけて上書きを行うと、呼び出し元のオリジナル変数の実体メモリが直接書き換わる「参照渡し」相当の挙動が成立します。重い構造体のコピーを回避する際や、状態を変更したい場合は必ずポインタ関数として定義します。
Q: Go言語のfuncでは、関数のオーバーロード(同名で引数や型が異なる関数の重複定義)はできますか?
A: できません。Go言語の設計思想は簡潔性を最優先するため、関数のオーバーロードは意図的に排除されています。同じパッケージ内に同一名の関数が複数あるとコンパイルエラー(redeclared in this block)になります。異なる型に対応させたい場合は、AddIntAddFloat のように明示的に名前を分けるか、インターフェースやジェネリクス(Generics: [T any])を用いた現代的な型抽象化技術を導入する必要があります。
Q: init() 関数という特殊な func があると聞きましたが、main() と何が違うのですか?
A: init() 関数は、パッケージがロードされた瞬間に、main() 関数よりも前にランタイムから自動的に一回だけ呼び出される特殊な初期化関数です。引数や戻り値は一切持てず、同一パッケージ内に複数記述することも可能です。グローバルな構成情報のセットアップや接続テストの事前確立に使用されます。

まとめ

  • func は、Go言語におけるすべてのロジック、手続き、メソッド、および匿名クローズクロージャを静的に宣言するための予約語。
  • 複数の戻り値をネイティブで返却できるため、他言語のような例外スローに依存しない、安全で予測可能なエラー制御フローを組み立て可能。
  • 関数名のファーストレター(大文字/小文字)によってパッケージ外部への可視性(スコープ制限)を決定する、極めてシンプルな可視性管理仕様を持つ。
  • すべての引数は値コピーとして処理される特性や、オーバーロードの不許可といった一貫性のある言語仕様により、初見のコードでも動作を追いやすい高い平易性を担保している。