LoginSignup
2
0

More than 3 years have passed since last update.

ぼく 「[]runeを範囲外でスライスしたら当然エラー出るだろうなあ」

Last updated at Posted at 2019-07-30

ぼく 「あれ?」

Go言語_[]runeのスライス
str := "0123456"
runes := []rune(str)

// 範囲内
fmt.Println(string(runes[0]))   // 0
fmt.Println(string(runes[3]))   // 3
fmt.Println(string(runes[4:6])) // 45
fmt.Println(string(runes[4:7])) // 456

// 範囲外、、のはずだけど
fmt.Println(string(runes[4:8]))   // 456  <= エラー出ないのか、、
fmt.Println(string(runes[10]))    // エラー: index out of range   <= わかる
fmt.Println(string(runes[10:20])) // (空文字が出力)

ぼく 「範囲外のインデックス指定してるんだから、普通 "xxx out of range" 的なエラーが出るでしょ」
ぼく 「Go言語、まさかスライスで範囲外指定してもいいように察してくれるのか、?」
ぼく 「ここ範囲外だからとりあえず空文字返しとこ、的な」
ぼく 「いやいやいや、、」
ぼく 「stringでやってみよ」

Go言語_string配列のスライス
strArray := []string{"0", "1", "2", "3", "4", "5", "6"}
// 範囲内
fmt.Println(strArray[0])  // 0
fmt.Println(strArray[3])  // 3
fmt.Println(strings.Join(strArray[4:6], ""))  // 45
fmt.Println(strings.Join(strArray[4:7], ""))  // 456

// 範囲外
fmt.Println(strings.Join(strArray[4:8], ""))  // エラー: slice bounds out of range
fmt.Println(strArray[10])  // エラー: index out of range
fmt.Println(strings.Join(strArray[10:20], ""))  // エラー: slice bounds out of range

ぼく 「普通にエラー出るな、、」
ぼく 「これならわかる、けどさっきの[]runeの範囲外スライスはなんでエラー出ないんだ」
ぼく 「スライスのスライスだからか、、? []runeは確か配列じゃなくてスライスだったな、(※配列の参照、Goには可変長配列がないので代わりにこれをつかう)」
ぼく 「、、stringスライスのスライスならどうだろ」

Go言語_stringスライスのスライス
var strSlice []string
strSlice = append(strSlice, "0", "1", "2", "3", "4", "5", "6")
fmt.Println(strings.Join(strSlice[4:8], ""))  // エラー: slice bounds out of range
fmt.Println(strSlice[10])  // エラー: index out of range
fmt.Println(strings.Join(strSlice[10:20], "")) // エラー: slice bounds out of range

ぼく 「変わらないな、、」
ぼく 「スライスのスライスは範囲外でもエラー出ない、とかいう話ではないのか」
ぼく 「なんでruneのスライスだけ、範囲外でスライスしてもエラー出ないんだろう」
ぼく 「(わからんtaskete)」

追記(2019/07/31) : 親切な方(aimofさん)降臨

スクリーンショット 2019-07-31 6.52.19.png

ぼく 「神は実在した…(激しく感謝)
ぼく 「aimofさんありがとうございます」
ぼく 「というかcapってなんだ」
ぼく 「キャップだから…帽子か?

調べてみた

  • cap ( capacity ) : 配列の容量
    ▲ 要素数(length)とは異なる

ぼく 「Go言語にはlengthだけじゃなくて、capacityとかあるのか…」
ぼく 「で、[]rune(str)でキャストするときには、なんか暗黙的にcapacity大きく取られてたってことか」
ぼく 「みてみよ」

str := "0123456"
runes := []rune(str)
fmt.Println(cap([]byte(str)))  // 32
fmt.Println(cap(runes))  // 32

// capacity内
fmt.Println(string(runes[4:32])) // 456

// capacity外
fmt.Println(string(runes[4:33])) // slice bounds out of range

ぼく 「ほんとだ、、runeスライスへのキャストで、だいぶ大きめにcapacity取られてるな」
ぼく 「32もある…」
ぼく 「なんでこの数なんだろ」

(下の方で少し試した(おまけ)ことあります)

とりあえずまとめ

  • []runeは範囲外でスライスしてもいいように察してくれる(エラー出ない)
  • それ以外の配列・スライスなら範囲外スライスは普通にエラーがでる
  • 配列・スライスのlen, capの問題だった
  • makeでcapを指定、とかではなく[]rune(str)でキャストすると、capが勝手にある程度の値を取ってくれる
  • len (=length) : 配列の要素数 ( ◁ わかる )
  • cap (=capacity) : 配列の容量 ( ◁ 知らなかった )
  • capの範囲内であれば、例え配列の要素が存在せずとも slice bounds out of range は出ない

補足

  • string: 文字列を示すbyteスライスのイミュータブル参照、Go言語ではstr[0]とか書くと1文字目ではなくひとつ目のbyte値が得られる
  • rune: Unicodeコードポイント、ざっくりいうと文字1つひとつに割り当てられたコード値. runeのスライスはstringに変換できる

追記(2019/07/31) : おまけ

// ▼ 1文字の場合
s := "1"
rs := []rune(s)

// length
fmt.Println(len(rs)) // 1

// capacity ( <= 1文字でもとりあえず32確保する模様)
fmt.Println(cap([]byte(s))) // 32
fmt.Println(cap(rs))        // 32


// ▼ 32文字の場合
st := "12345678901234567890123456789012"
rus := []rune(st)

// length
fmt.Println(len(rus)) // 32

// capacity ( <= 32文字なら、まだcapacityも32のまま)
fmt.Println(cap([]byte(st))) // 32
fmt.Println(cap(rus))        // 32


// ▼ 33文字の場合
str := "123456789012345678901234567890123"
runes := []rune(str)

// length
fmt.Println(len(runes)) // 33

// capacity ( <= 32を超えて初めて増加! )
fmt.Println(cap([]byte(str))) // 48
fmt.Println(cap(runes))       // 36
2
0
2

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