KDOC 253: GoでN段階に可変長引数を渡す

この文書のステータス

  • 作成
    • 2024-10-10 貴島
  • レビュー
    • 2024-10-28 貴島

概要

Goの可変長引数は、関数定義側では左側に ... をつけて示す(...args)。関数内ではスライスとして使える。

また、関数呼び出し側ではスライスの右側に ... をつけてスライスの要素を可変長引数として渡せる(slice...)。

package main

import "fmt"

// func Print(a ...any) (n int, err error) {}

func main() {
        fmt.Print("a", "b", "c")
        fmt.Println() // 改行
        fmt.Print([]any{"a", "b", "c"}...)
}
abc
abc

多くのユースケースをサポートする、オプショナルな項目の多いタイプのプログラムでは、この構文を多用するものがある1。特定のオプショナルな項目の組み合わせを関数化して、さらにその関数にオプショナルな項目をつける、というようなことだ。そういう場合は直感的でなく、少し目を慣らさないといけない。コードで見てみる。

package main

import "fmt"

func main() {
        run1("a", "b", "c")
        fmt.Println() // 改行
        run2("c")
}

func run1(opts ...string) {
        for _, str := range opts {
                fmt.Print(str)
        }
}

func run2(opts ...string) {
        run1(append([]string{"a", "b"}, opts...)...)
}
abc
abc

run1() では “a”, “b”, “c” がオプショナルだが、 run2() では “c” だけがオプショナルになっている。 run2() では “b”, “c” は自動で渡される。利用者は長大なオプショナル引数を渡すことなく、プリセットの機能をすぐ利用できる。いっぽうで、細かく引数を指定して自分のケースに合わせたい利用者の要望も満たせている。

1つずつ見る。

  1. opts -> スライス
  2. ...opts -> スライスを展開してappendの可変長引数となる
    • append のシグネチャ func append(slice []Type, elems ...Type) []Type
  3. append([]string{"a", "b"}, opts...) -> これは合体したスライスとなる
    • つまり []string{"a", "b", "c"} となる
  4. append([]string{"a", "b"}, opts...)... -> スライスを展開してrun1の可変長引数となる
    • つまり run1("a", "b", "c") というように呼び出す

関連

Footnotes:

1

例えばebitenui/ebitenui