Go の可変長引数(variadic functions)

Go では、関数の引数の数を固定せず、任意の個数の引数を受け取れる「可変長引数」がサポートされている。fmt.Println のように、いくつでも値を渡せる関数は、この仕組みで実現されたものだ。

可変長引数の基本構文

引数の型の前に ... を付けると、その引数は可変長になる。関数の内部では、可変長引数はスライスとして扱われる。

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

func main() {
    fmt.Println(sum(1, 2, 3))       // 6
    fmt.Println(sum(10, 20))         // 30
    fmt.Println(sum())               // 0(引数なしも可)
}

引数をまったく渡さない場合、関数内のスライスは長さ 0 の空スライスになる。nil ではないため、range でループしてもパニックにはならない。

可変長引数の位置の制約

可変長引数は、関数の最後のパラメータにのみ指定できる。通常の引数と組み合わせることは可能だが、可変長引数を先頭や中間に置くことはできない。

// OK: 通常引数の後に可変長引数
func greet(prefix string, names ...string) {
    for _, name := range names {
        fmt.Printf("%s, %s!\n", prefix, name)
    }
}

func main() {
    greet("Hello", "Alice", "Bob", "Charlie")
}

この制約があることで、コンパイラはどこまでが通常の引数で、どこからが可変長引数かを明確に区別できるようになっている。

スライスを可変長引数に展開する

既存のスライスを可変長引数の関数に渡したいときは、スライスの後ろに ... を付ける。これにより、スライスの各要素が個別の引数として展開される。

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

func main() {
    numbers := []int{5, 10, 15}
    result := sum(numbers...) // スライスを展開して渡す
    fmt.Println(result)       // 30
}

numbers... のように書くと、sum(5, 10, 15) と呼び出したのと同等になる。ただし、展開と個別の値を混在させることはできない点に注意が必要だ。

個別に渡す

sum(1, 2, 3) のように値を直接並べる。引数の数が少ないときに便利

スライスを展開

sum(nums…) のようにスライスごと渡す。動的に数が決まる場合に使う

標準ライブラリでの活用例

Go の標準ライブラリには、可変長引数を活用した関数が数多く存在する。

// fmt.Println: 任意の数の値を出力
fmt.Println("a", "b", "c")

// fmt.Sprintf: フォーマット文字列と可変長引数
msg := fmt.Sprintf("%s is %d years old", "Alice", 30)

// append: スライスに要素を追加
s := []int{1, 2}
s = append(s, 3, 4, 5)

// append でスライス同士を結合
a := []int{1, 2}
b := []int{3, 4}
a = append(a, b...)

append はビルトイン関数だが、可変長引数の仕組みと同じインターフェースを持っている。第 2 引数以降に複数の値を渡すことも、スライスを ... で展開して渡すことも可能だ。

any 型との組み合わせ

型の異なる引数を受け取りたい場合は、anyinterface{} のエイリアス)と組み合わせる。fmt.Println が任意の型を受け取れるのも、この方法による。

func debugLog(values ...any) {
    for i, v := range values {
        fmt.Printf("[%d] %T: %v\n", i, v, v)
    }
}

func main() {
    debugLog("hello", 42, true, 3.14)
    // [0] string: hello
    // [1] int: 42
    // [2] bool: true
    // [3] float64: 3.14
}

any を使うと柔軟性は高まるが、型安全性は失われる。実行時に型アサーションや型スイッチで型を判定する必要があるため、必要な場面に絞って使うのが望ましい。可変長引数は Go のシンプルな構文のなかで柔軟性を確保する重要な仕組みであり、標準ライブラリの設計思想にも深く組み込まれている。