👋

Go: pack/unpack演算子と可変長引数関数・Slice

2021/06/21に公開

はじめに

Goのプログラムを読んでいて初めて3つのdot ... に出会って、「ん?、なんだろう」ってなったことのある人も多いのではないでしょうか。
可変長の引数をとる関数などでも使われていますが、ちょっと正体を調べてみたいと思います。

pack・unpack演算子

まずは、pack/unpack演算子の理解が必要です。
Go言語では、3つのdot ...を、pack演算子(pack operator)またはunpack演算子(unpack operator)と呼びます。これが正体なのですがどのような使われ方をするのか見てみます。

...は、pack(詰め込む・まとめる) とunpack(解凍する・展開する) の両方の演算子を意味します。それを区別するには、3つのドット (...) がどこに位置しているのか確認します。

演算子の配置場所 種類 機能
変数の先頭 (...xxx) pack 演算子 まとめる役割
変数の最後尾 (xxx...) unpack 演算子 展開する役割

pack演算子の例

可変個引数関数(variadic function)がそれです。

func showParams(params ...int)

メソッド:showParams は params (数値型のpack演算子)を引数を受け取る という内容になります。もう少し突っ込んでみると、
「全ての int型の 引数を詰め込んだ変数 params:sliceの[]intとしてpackした showParamsメソッドを定義する」という言い方もできると思います。(ややこしいかも)

unpack演算子の例

末尾にある場合、スライスを unpack(展開・解凍・バラす)します。
unpackの場合は、先程の メソッド:showParams の呼び出しの時に必要になります。

// スライス
a := []int{1, 2, 3, 4, 5}
// このように引数にはunpack:展開した形で引数として渡します。
showParams(a...)

可変個引数関数(Variadic function)

もう前述の説明で十分だと思いますが、実例など示してみたいと思います。
フォーマットは下に示すようにします。引数の指定をpack演算子と引数の型で指定します。

func method(args ...Type)

実際にコードを記述してみます。

// 引数の中身を出力する関数です
// sはpack演算子で引数の型はint型です
func showSlice(s ...int) {
    // sはスライスとして扱うことができます
    for index, value := range s {
        fmt.Printf("s[%d] = %d\n", index, value)
    }
}

func main() {
    a := []int{1, 2, 3, 4, 5}
    // これはコンパイルエラーになります
    showSlice(a)
    // cannot use a (type []int) as type int in argument to showSlice
    // 引数はsliceではなく、int型のまとまり でなければいけません
    
    // これはどうでしょう?
    showSlice(1)
    // これは、s[0] = 1 と出力されます。
   
    // これはどうでしょう?
    showSlice(1,2,3)
    // s[0] = 1
    // s[1] = 2
    // s[2] = 3
    // となります。まさに可変個引数ですね。
    
    // では、sliceはどの様にわたしますか?
    // ここでunpack演算子を使って、引数を展開します。
    showSlice(a...)
    // s[0] = 1
    // s[1] = 2
    // s[2] = 3
    // s[3] = 4
    // s[4] = 5
    // つまり、下に示す方法と同じことですね
    showSlice(1,2,3,4,5)
}

Go Playground で試してみください。

公式のサンプルにも可変長引数関数については、章で紹介されています。

スライス(Slice)

sliceのappend関数もみてみます。
組込み関数のappendはsliceの後方に要素を追加していきます。ドキュメント

append([]Type, args, arg2, argsN)

append関数の第1引数は、Type型の slice です。そして第2引数以降は可変数の引数が存在する可能性があります。

func main() {
    a := []int{1, 2, 3, 4, 5}
    // これは
    a = append(a, 6, 7)
    // [1 2 3 4 5 6 7] となります
}

では、s1 というスライスに s2 というスライスを追加するにはどうしたらいいでしょうか?

ビルトインの append メソッドで追加する要素がsliceの場合は下に示すコードになります。ドキュメント

func append(slice []Type, elems ...Type) []Type

つまり、前述の追加関数の様にsliceを引数として渡すことはできません。代わりに、unpack演算子...を使用して、スライスを一連の引数に展開します。追加するsliceの全ての要素を展開するというイメージですね。

コードとしては下のようになります。

append(s1, s2...)

実際のコードは以下です。

func main() {
    s1 := []int{1, 2, 3, 4, 5}
    s2 := []int{10, 20, 30, 40, 50}
    
    // unpack演算子を使い引数とします
    s1 = append(s1, s2...)
    // [1 2 3 4 5 10 20 30 40 50]
}   

Go Playground

1点注意があって、可変長引数関数では、最後の引数だけを可変長型として設定することができます。そういうわけで、複数引数がある場合、1番最初に可変長型が位置をとることはできません。

まとめ

今回は、pack演算子・unpack演算子と可変個引数関数とスライスを深堀りしてみました。初めて出会う「演算子」でしたが、今回いろいろと調べることで親しみが湧いてくるような感覚が残りました。まだまだ知らないことがありそうですが、理解が深まることで良いコードを書くことの土壌になるような気がしました。

Discussion