LoginSignup
22
7

More than 5 years have passed since last update.

スライスを返すときはcapに注意しよう

Posted at

次のコードの出力を予想してみてください。

package main

import (
    "fmt"
)

func splitbyzero(xs []int) ([]int, []int) {
    for i, x := range xs {
        if x == 0 {
            return xs[:i], xs[i:]
        }
    }
    return xs, []int{}
}

func main() {
    data := []int{1, 2, 3, 0, 1, 2, 3}
    x, y := splitbyzero(data)
    fmt.Printf("x: %v\n", x)
    fmt.Printf("y: %v\n", y)
    x = append(x, 42)
    fmt.Printf("x: %v\n", x)
    fmt.Printf("y: %v\n", y)
}

結果はこうなります。

x: [1 2 3]
y: [0 1 2 3]
x: [1 2 3 42]
y: [42 1 2 3]

x = append(x, 42) により、y 側のデータが上書きされてしまっています。

これは、 xs[:i] によって作られるスライスの capacity が cap(xs) になっており、 append で再利用できると判断されているからです。

Go プログラマーは一般的に append を安全な操作だと認識しているので、 append() によって他のデータを破壊してしまう可能性があるスライスを返してはいけません。

この場合、 xs[:i]xs[:i:i] に書き換えれば、 x の capacity は len と同一になるので安全になります。

このスライスの構文は "Full slice expression" と呼ばれます。(Slice expressions を参照)

Full slice expression の3つ目の添字は、 capacity ではなくて max です。途中をスライスするときは、 xs[a:b:b] になります。 (xs[a:b:b-a] ではありません)

個人的には、よく使われる max を省略する方のスライス式 xs[begin:end] で、 max が end ではなく cap(xs) として扱われるのはGoの設計ミスだと思います。
テストしてても気づきにくいバグを生み出しやすい () ので、デフォルトが安全側で、パフォーマンスを気にするときだけ再利用可能な領域を指定する方が良かった。

22
7
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
7