Golang 入門

Go 可変長引数関数

Go言語において、可変長引数関数(Variadic functions)は、関数が不特定の数の引数を受け取ることができる非常に柔軟な手法を提供します。関数が処理すべき入力値の数が事前に決まっていない場合、この機能は極めて重要になります。

可変長引数関数の定義と使用方法を理解することは、より適応性が高く、再利用性の高いコードを記述するための鍵となります。

本章では、関数の基礎を踏まえ、Go言語における可変長引数関数の構文、テクニック、そして実戦的なアプリケーションについて全面的に探索していきます。

1. 可変長引数関数の理解

可変長引数関数とは、最後のパラメータとしてゼロ個以上の値を受け取ることができる関数のことです。Go言語での実装は非常にシンプルです。関数シグネチャの最後のパラメータの型の前に、省略記号 ... を付けるだけです。

1.1 構文と宣言

可変長引数関数を宣言する基礎的な構文は以下の通りです:

func myFunc(arg1 type1, arg2 type2, variadicArg ...type3) returnType {
    // 関数本体のロジック
}

この構文構造において:

  • arg1arg2 は通常の固定引数です。
  • variadicArg は可変長引数であり、type3 型の値をゼロ個以上受け取ることができます。

核心メカニズム: 関数本体の内部において、この可変長引数 variadicArg は Go 言語によって自動的にスライス (Slice) []type3 として処理されます。

1.2 実装例:数値の合計を計算する

任意の数の整数を受け取り、その合計を計算するシンプルな可変長引数関数を書いてみましょう。

package main

import "fmt"

// sum 関数は不特定の数の整数の合計を計算します。
func sum(numbers ...int) int {
    total := 0
    // 関数内部で、numbers は []int スライスとして反復処理されます
    for _, num := range numbers {
        total += num
    }
    return total
}

func main() {
    fmt.Println(sum())           // 出力: 0 (0個の引数を渡す)
    fmt.Println(sum(1, 2, 3))    // 出力: 6 (3個の引数を渡す)
    fmt.Println(sum(4, 5, 6, 7)) // 出力: 22 (4個の引数を渡す)
}

この例では:

  • sum 関数は可変長引数 numbers ...int を受け取ります。
  • 関数内部では、numbers は整数スライス []int として扱われます。
  • 関数は for...range ループを使用してスライスを走査し、すべての数値を累計します。

2. スライスを可変長引数関数に渡す

すでに手元に既存のスライスがある場合、それを直接可変長引数関数に渡すにはどうすればよいでしょうか? その場合は、再度 ... オペレータを使用してスライスを個別の引数に「展開」 (Unfurl/Spread) します。

package main

import "fmt"

func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

func main() {
    nums := []int{10, 20, 30}
    
    // nums... を使用してスライスを展開し、可変長引数関数に渡します
    fmt.Println(sum(nums...)) // 出力: 60
}

この例では:

  • スライス numssum 関数に渡す際、nums... と記述しています。
  • 末尾の ... オペレータがスライスを「バラバラ」にし、10, 20, 30 という 3 つの独立した引数として関数に送り込みます。

3. 実践的なユースケース

可変長引数関数の強力な汎用性を示すために、実際の開発におけるさらなる例を見てみましょう。

3.1 文字列のフォーマット

可変長引数関数は文字列のフォーマットによく使われます。Go標準ライブラリの fmt.Printf はその最も古典的な例です。

package main

import "fmt"

// format 関数はフォーマットテンプレートと任意の数の引数を結合します。
func format(formatString string, args ...interface{}) string {
    return fmt.Sprintf(formatString, args...)
}

func main() {
    name := "Alice"
    age := 30
    message := format("名前: %s, 年齢: %d", name, age)
    
    fmt.Println(message) // 出力: 名前: Alice, 年齢: 30
}

この例では:

  • format 関数は通常の文字列引数 formatString と、可変長引数 args ...interface{} を受け取ります。
  • interface{}(空インターフェース)は任意のデータ型を象徴します。つまり、数値、文字列、ブール値など、あらゆるものを渡すことができます。
  • 関数内部で fmt.Sprintf を呼び出し、受け取った引数を args... でそのまま展開して渡しています。

3.2 最大値の取得

もう一つの非常に一般的なユースケースは、数値グループの中から最大値を見つけることです。

package main

import "fmt"

// max は不特定の数の整数の中から最大値を見つけます。
func max(numbers ...int) int {
    if len(numbers) == 0 {
        return 0 // 引数が渡されなかった場合は 0 を返す
    }
    
    maxValue := numbers[0]
    for _, num := range numbers {
        if num > maxValue {
            maxValue = num
        }
    }
    return maxValue
}

func main() {
    fmt.Println(max(1, 5, 2, 8, 3)) // 出力: 8
    fmt.Println(max())              // 出力: 0
}

この例では、max 関数が受け取ったすべての数値を走査し、maxValue を更新し続けます。引数が一つも渡されなかった(スライスの長さが 0)場合は、安全に 0 を返します。

3.3 文字列の結合

不特定の数の文字列を指定した区切り文字で結合することも可能です。

package main

import (
    "fmt"
    "strings"
)

// concatenate は指定されたセパレータを使用して、不特定の数の文字列を結合します。
func concatenate(separator string, stringsToJoin ...string) string {
	return strings.Join(stringsToJoin, separator)
}

func main() {
	result := concatenate("-", "apple", "banana", "cherry")
	fmt.Println(result) // 出力: apple-banana-cherry
	
	result = concatenate(",", "one", "two")
	fmt.Println(result) // 出力: one,two
	
	result = concatenate(" ", "hello", "world")
	fmt.Println(result) // 出力: hello world
}

ここでは concatenate 関数がセパレータ separator と可変長の文字列引数 stringsToJoin を受け取り、標準ライブラリの強力な関数 strings.Join を利用して一括で結合処理を行っています。