フューチャー技術ブログ

Go 1.22 リリース連載 slicesのマイナーアップデート

The Gopher character is based on the Go mascot designed by Renée French

はじめに

TIGの辻です。Go1.22連載の2本目です。

この記事では、マイナーアップデートから slices パッケージを取り上げて紹介します。

slices のアップデート内容

  • slice を連結する Concat() API が追加になった(#56353)
  • slice の長さを小さくする関数で、破棄された slice の要素をゼロ値とする(#63393)
  • Insert() で引数が範囲外の場合に、常に panic させる(#63913)

slice を連結する Concat() API が追加になった(#56353)

以下の API が追加になりました。渡された slice を連結して新しい slice を返却します

func Concat[S ~[]E, E any](slices ...S) S

コード例です

package main

import (
"fmt"
"slices"
)

func main() {
n1 := []int{1, 2}
n2 := []int{10, 20}
n3 := []int{100, 200}
result := slices.Concat(n1, n2, n3)
fmt.Println(result) // [1 2 10 20 100 200]
}

ちなみにGo 1.21ですと appned() では複数の slice を連結できず、slice を unpack して以下のように実装する必要がありました。

package main

import (
"fmt"
)

func main() {
n1 := []int{1, 2}
n2 := []int{10, 20}
n3 := []int{100, 200}
result := append(n1, append(n2, n3...)...)
fmt.Println(result) // [1 2 10 20 100 200]
}

slice の長さを小さくする関数で、破棄された slice の要素をゼロ値とする(#63393)

以下のAPIの機能改善です。slice の長さが小さくなる関数で、破棄すべき slice の要素をゼロ値でクリアするようになっています

  • Delete()
  • DeleteFunc()
  • Compact()
  • CompactFunc()
  • Replace()

Delete() のコード例です

Go1.21

package main

import (
"fmt"
"slices"
)

func main() {
src := []int{1, 2, 3, 4, 5, 6}
result := slices.Delete(src, 1, 3)

// 元の slice では Delete() 後に元々あった 4, 5番目の要素が残っている
fmt.Println(src) // [1 4 5 6 5 6]

fmt.Println(result) // [1 4 5 6]
}

Go1.22

package main

import (
"fmt"
"slices"
)

func main() {
src := []int{1, 2, 3, 4, 5, 6}
result := slices.Delete(src, 1, 3)

// 元の slice では Delete() 後に元々あった 4, 5番目の要素はゼロ値でクリアされている
fmt.Println(src) // [1 4 5 6 0 0]

fmt.Println(result) // [1 4 5 6]
}

横道にそれますが Delete() に関して CL#541477 からコードの変更内容を見てみました。
https://go-review.googlesource.com/c/go/+/541477

変更前は要素を除いた値を appned() しているだけですが、変更後は明示的に clear() しています

  • 変更前
func Delete[S ~[]E, E any](s S, i, j int) S {
_ = s[i:j] // bounds check

return append(s[:i], s[j:]...)
}
  • 変更後
func Delete[S ~[]E, E any](s S, i, j int) S {
_ = s[i:j] // bounds check

oldlen := len(s)
s = append(s[:i], s[j:]...)
clear(s[len(s):oldlen]) // zero/nil out the obsolete elements, for GC
return s
}

Insert() で引数が範囲外の場合に、常に panic させる(#63913)

Insert() APIの機能改善です。
Go1.21では範囲外の Index が渡されても、挿入する要素が存在しない場合は panic しませんでした。
ただドキュメント上での説明では Insert panics if i is out of range とあり動きにドキュメントと動作に乖離がありました。
https://pkg.go.dev/slices@go1.21.0#Insert

Go1.21では以下のコードが実行できます

Go1.21

package main

import (
"fmt"
"slices"
)

func main() {
s := []string{"a", "b", "c"}
s = slices.Insert(s, -1) // panic は起きない

fmt.Println(s) // [a b c]
}

ただし範囲外の Index に値を追加する場合はGo.1.21でも panic になります

package main

import (
"fmt"
"slices"
)

func main() {
s := []string{"a", "b", "c"}

// panic: runtime error: slice bounds out of range [:-1]
s = slices.Insert(s, -1, "x") // panic が起きる

fmt.Println(s)
}

Go1.22

Go1.22では範囲外の Index が指定されている場合は要素の有無に関わらず、panic するようになります

package main

import (
"fmt"
"slices"
)

func main() {
s := []string{"a", "b", "c"}

// panic: runtime error: slice bounds out of range [-1:]
s = slices.Insert(s, -1) // panic が起きる

fmt.Println(s)
}

おわりに

slices パッケージの機能追加/更新を紹介しました。
slices パッケージはGo1.21で標準ライブラリに追加されましたが、ユーザーに見える/見えにくい内容含めて、まだまだ進化しそうでこれからも楽しみですね