1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Go】一体いつからappendの引数に与えた変数が不変だと錯覚していた?

Posted at

結論

slice =  append(slice[:idx], slice[idx+1:]...)

上記のようにできない場合で、どうしても元のスライスを保持したいときはmake&copyしましょう

slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
newSlice := make([]int, len(slice))
copy(newSlice, slice)

はじめに

スライスを使うときに無意識でappendを使うかと思います。
単純に追加するだけであれば、至って普通の挙動を示しますが、とある条件の時に
想定しない挙動になることがあります。

実験内容

配列の中から特定のインデックスにある値を削除する処理です。
例えば以下のようなコードがあった場合はどうでしょうか?

main.go
package main

import "fmt"

func main() {
	slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	fmt.Println(slice)
	idx := 5
	for k, v := range slice {
		if k == idx {
			fmt.Println("before:", slice[k], v) //5 5
			fmt.Println("append", append(slice[:idx], slice[idx+1:]...)) //特定の配列を削除する
			fmt.Println("original slice", slice)
			fmt.Println("after", slice[k], v) // この出力は?
		}
	}
}

普通は

slice = append(slice[:idx], slice[idx+1:]...)

のようにするところですが、例えば以下のようなケースが想定されます。

  1. 共通化するべく、別メソッドで作る
  2. 削除する前と後を両方保持しておきたい

感覚的にはbeforeとafterは全く同じになると思うかもしれません。

#実験結果
実際に実行してみます

[0 1 2 3 4 5 6 7 8 9]
before: 5 5
append [0 1 2 3 4 6 7 8 9]
original slice [0 1 2 3 4 6 7 8 9 9]
after 6 5

afterの値が「5 5」ではなく「6 5」となります。元のスライスもおかしくなっていることがわかります。

なぜ普遍だと錯覚していた?

A Tour of Go より

もし、元の配列 s が、変数群を追加する際に容量が小さい場合は、より大きいサイズの配列を割り当て直します。 その場合、戻り値となるスライスは、新しい割当先を示すようになります。

つまり言い換えるとappendの引数にあるスライスの容量が新たに割り当てるまでもなく大きい場合は割り当て直さない。

append にで指定した元の配列が変更されてしまい、
感覚と反する結果になっています

1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?