Tour of Goのスライスの挙動で引っかかってたら、色々分かった話

#目次
- [はじめに](#はじめに)
- [対象の読者](#対象の読者)
- [この記事で伝えたいこと](#この記事で伝えたいこと)
- [スライスとは](#スライスとは)
- [スライスのスライシング](#スライスのスライシング)
- [どこで引っかかったか](#どこで引っかかったか)
- [スライスの代入は元の配列のポインタを渡す](#スライスの代入は元の配列のポインタを渡す)
- [スライシングの仕様](#スライシングの仕様)
- [まとめ](#まとめ)
- [参考](#参考)

#はじめに
この記事は [カオナビ Advent Calendar 2021](https://qiita.com/advent-calendar/2021/kaonavi) 5日目です。

初めまして！カオナビに新卒で入って現在2年目の@summer_shineです。

最近、自分が新しく所属することになったチームで、初めてGoを触ることになりました。
その際に、[A Tour of Go](https://go-tour-jp.appspot.com/list) (Goのチュートリアルができる場所)のスライスの項目でそんな挙動するの！？ってなり、引っかかった箇所があったので、それについて調べてみました。

自分と同じように、つまづいた人がいれば参考にして頂けると嬉しいです。
というわけで、5日目はGoのスライスの話をしようと思います！

#対象の読者
- Goの初学者
- スライスについて学び始めた人
- Tour of GoのSlice length and capacityの挙動が良く分からなかった人 ←ピンポイント

#この記事で伝えたいこと
- 重要：**スライスの代入について**
- 重要；**スライスのインデックスの仕様について**
- スライスについて
- スライスのスライシングについて

#スライスとは
スライスについての記事は調べると沢山出てきますが、自分が引っかかったコードについて理解してもらう為にも、最低限必要なスライスの知識については触れておきます。

**スライスとはGoにおける可変長の配列**のようなものです。

スライスは配列をラップ(配列の機能を含みつつ、別のものとして提供)してできています。

**Goの配列は固定長**です。
普段コードを書く際には利便性が高いという理由で、スライスの方が使われることが多いです。(後からスライスに要素を追加したりできるので)

スライスの宣言の仕方は簡単で、配列宣言時の要素数を省略することで、使うことができます。（make関数を使うことでも作成可能）

```golang
// 配列
favoritePokemon := [3]string{"カイオーガ", "ウルガモス", "シャンデラ"}

// スライス
favoritePokemon := []string{"カイオーガ", "ウルガモス", "シャンデラ"}

// make([]T, len, cap)を使ったスライスの宣言。 T、len、capにはそれぞれ型、長さ、容量を指定
favoritePokemon := make([]string, 3, 3)
```

スライスはポインタ(ptr)と長さと(len)容量(cap)で構成されています。
![slice-struct (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2322658/8f4041b5-5535-b858-d261-3b999d935029.png)

- ptr : 元となる配列のアドレスを持つ
- len : スライスが参照している配列の要素の数
- cap : 元になる配列内の要素の数。ptrによって参照される要素から数えた数。

# スライスのスライシング
スライスはスライシングをすることでスライスの値を切り出して渡すことができます。

スライスシングの指定の仕方は`s[low:high]`でスライスに対してインデックスを指定することで取得できます。

```golang
// スライス 
favoritePokemon := []string{"カイオーガ", "ウルガモス", "シャンデラ"}

// 炎タイプだけスライシング 　
fireType := favoritePokemon[1:3]// ["ウルガモス", "シャンデラ"]
```
 

# どこで引っかかったか
次に、まず自分がTour of Goのスライスの項目の挙動で引っかかった箇所を見てみたいと思います。
上記の項目のスライスの宣言とスライシングが分かっていれば、該当の箇所は読めると思います。（コードはTour of Goのままですが、コメントを一部編集しています）

```golang
package main

import "fmt"

func main() {
    // スライスを代入
	s := []int{2, 3, 5, 7, 11, 13} 
	printSlice(s) //   [2 3 5 7 11 13]

	// スライスの[low:high]を指定してスライシング
	// この場合は要素数0から0、つまり空のスライスをsに再代入
	s = s[:0]
	printSlice(s) // []

    // 問題の箇所
	// 先ほど、空を代入したスライスの０番目から4番目までを代入!?
	s = s[:4]
	printSlice(s) // [2 3 5 7] ← !?!?!?!?

	// 再代入したものをスライシング
	s = s[2:]
	printSlice(s)
}

func printSlice(s []int) {
    // len()とcap()はスライスを受け取り、len()はスライスの長さ、cap()は容量を返す
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}	
```

実行結果

```go
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
```

実行結果では確かに空が出力されていたのに、そのスライスに対して`[:4]`とスライシングすると最初に代入していた値が取れています。

自分は最初、何が起こったんだ・・・となってましたが、調べたら謎が解けたので次以降の項目で、この現象がなぜ起こったのかを順を追って話したいと思います。

# スライスの代入は元の配列のポインタを渡す
スライスを変数に代入するときに、その変数に渡されるスライスの値（例：[2, 3, 5, 7]）は**値のコピーを渡すのではなく、元の配列のポインタを渡しています。**

その為、スライスを代入した状態でそれぞれの値が指し示すアドレスを表示してみると、同じものが出力されます。
同じポインタを持っていると言うことは`s2`のを値を変えると`s1`の値も変わります。

```golang
// 配列へptr、len:6、cap:6をもつスライスを代入
s1 := []int{2, 3, 5, 7, 11, 13}  // [2 3 5 7 11 13]

// スライシングして再代入
s2 := s1[:1]  // [2]

// アドレスを表示すると同じ場所を指している
fmt.Printf("%p \n", s1) // 例： 0xc0000b6030
fmt.Printf("%p \n", s2) // 例：　0xc0000b6030
```

つまり、自分が引っかかった場所である以下のGo tourのコードは、値としては空の配列であるが、ポインタが指す先は元の配列のアドレスを持っていた、という事になります。

```golang
s := []int{2, 3, 5, 7, 11, 13} // スライスを代入
printSlice(s) //   [2 3 5 7 11 13]

// スライシングしつつ、元の配列のポインタも渡している
s = s[:0]
printSlice(s) // []
```
再代入された`s`が、ポインタとしては元の配列を指していることは分かったと思うのですが、空のスライスに対して`s = s[:4]`とスライシングが出来てしまう事には、まだ疑問を持っている人もいるかもしれません。
（少なくとも自分は、空からなんで値取れるんだ・・・ってなってました）


ちなみに、スライスの複製をしたい場合は`copy()`を使って作ることがきます。
`copy()`だとそれぞれのスライスが別々のポインタを持つので、片方を変更しても、もう片方が変わるという事にはなりません。

```golang
// スライス
s1 := []int{1, 2, 3, 4, 5, 6}

// 複製したいスライスの代入先の初期化
s2 := make([]int, len(s1))

//  copy(複製の代入先、複製したいスライス)
copy(s2, s1)
```
# スライシングの仕様
スライシング(`s[low:high]`)でスライスの値を切り出せると言う話をしたと思いますが、その際に指定できるインデックス(`low:high`の数値 )の範囲は、要素の数`len(s)`ではありません。

スライスの場合、指定できるインデックスの上限は容量`cap(s)` 、つまり元になる配列の`ptr`によって参照される要素から数えた値によります。

> For slices, the upper index bound is the slice capacity cap(a) rather than the length.
>(和訳)スライスの場合、インデックスの上限は、長さではなくスライスの容量cap(a)となります。
>[The Go Programming Language Specification - go.dev より](https://go.dev/ref/spec#Slice_expressions)

- **スライスの代入は元の配列のポインタを渡すこと**
- **スライシングのインデックスの範囲は容量cap(s) によること**

以上の２つを踏まえてもう一度go tourのコードを見ていきましょう。（一部省略）

```golang
// スライスを代入
s := []int{2, 3, 5, 7, 11, 13} // len=6 cap=6 [2 3 5 7 11 13]

// 空のスライスをsに再代入
s = s[:0]// len=0 cap=6 []

// 問題の箇所
s = s[:4]
```
5行目で`s = s[:0]`の再代入により、sは基の配列の`ptr`(指し示す先は[])、`len` ([ ]なので０)、`cap` (ptrからみた元の配列の要素数なので６）を持っている状態です。

8行目の`s = s[:4]` は、再代入された`s`の`len` は０であるが、スライスのインデックスの範囲は`len`ではなく`cap`を見るので、ポインタが指し示す基の配列に対して`[:4]` というスライシングを行ったという事になります。

その為、**空の配列から値を取っているように見えた**。という事になります。

これでスライスの挙動で引っかかっていたものが解消されました（チョースッキリ!!）
 

ビミョーに話がズレますが、スライシングで追加で気をつけて欲しいことは、インデックスを省略した場合です。

```golang
// スライスを代入
s := []int{2, 3, 5, 7, 11, 13} // len=6 cap=6 [2 3 5 7 11 13]

// 空のスライスをsに再代入
s = s[:0] // len=0 cap=6 []

fmt.Println(s4[:cap(s)])// [2 3 5 7 11 13]
fmt.Println(s4[:])// 　[]     　　[2 3 5 7 11 13]とはならない
```
`[:]`は全要素を取り出したい時に使ったりしますが、この時に省略した`low`と`high`はそれぞれ`low = 0`、`high = len(s)` として扱われます。その為、5行目で空を再代入した`s`は`len`が０な為、`[:]`で取り出しても空のままとなります。
#まとめ
- **スライスの代入は配列のポインタを渡す！**
- **スライシングのインデックスの上限はlenではなくcap！**
- **スライスの再代入の挙動には気を付けよう！**

今回初めて記事を書く事になって色々と大変でしたが、学びがありました。
気になった点などあれば、コメントよろしくお願いします！
#参考
[https://go.dev/blog/slices-intro](https://go.dev/blog/slices-intro)
[https://go.dev/ref/spec#](https://go.dev/ref/spec#)