

下記のコードにおいて、(2)はOKで、(3)がruntime errorになるのはどのような理由によるものでしょうか？ Goのバージョンは"go1.11
darwin/amd64"です。

[code]

    package main
    
    import "fmt"
    
    func main() {
        a := "a"
        fmt.Println(a[:len(a)])      // (1) "a"
        fmt.Println(a[len(a):])      // (2) ""
        // fmt.Println(a[len(a)])    // (3) panic - index out of range
        // fmt.Println(a[len(a)+1:]) // (4) panic - slice bounds out of range
    }
    
[/code]


CC BY-SA 4.0
2019-04-21T10:59:04.097
54364
2019-04-21T17:59:27.730
2019-04-21T11:27:04.253
3060
33019
question
go
Go言語の簡易スライス式(s[n:m])について


Go言語はほとんど使ったことがないんですが、

  * 配列のインデックスとしての有効値は`0`から`len(a)-1`まで
  * 範囲を指定する場合、(左端)→開始位置のインデックス、(右端)→終了位置のインデックス **+1** で指定する

と言う場合には同じなので。

要素が複数あった方がわかりやすいように思います。

[code]

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        a := "abc"
        fmt.Println(len(a)) //->3
        fmt.Println(a[0]) //->97 ('a')
        fmt.Println(a[1]) //->98 ('b')
        fmt.Println(a[2]) //->99 ('c')
        //fmt.Println(a[3]) //panic: runtime error: index out of range
        fmt.Println(a[0:]) //->abc
        fmt.Println(a[:3]) //->abc
        fmt.Println(a[0:3]) //->abc
        fmt.Println(a[1:]) //->bc
        fmt.Println(a[1:3]) //->bc
        fmt.Println(a[2:]) //->c
        fmt.Println(a[2:3]) //->c
        fmt.Println(a[3:]) //->
        fmt.Println(a[3:3]) //->
        //fmt.Println(a[4:]) //panic: runtime error: slice bounds out of range
    }
    
[/code]

今の場合`a`(`len(a)==3`)のインデックスはこのように割り振られます。

[code]

    |'a'|'b'|'c'|何もない
     [0] [1] [2] [3]
    
[/code]

`a[len(a)]`(==`a[3]`)は要素のないところですから当然エラーになります。

だとすると逆に`a[:len(a)]`や`a[len(a):]`はなぜエラーにならないのかですが、上にあげた2番目のルールがあるからです。

`a[:len(a)]`は`len(a)==3`のとき、`a[0:3]`と同じです。

[code]

    |'a'|'b'|'c'|何もない
     [0]←ここから
                 [3]←ここの1個前まで
    
[/code]

`a[3]`には要素は何もないのですが、範囲としてはその1個前までを表すので"abc"を表すことになります。

当然`3`が指定できないと、「最後まで全部」を表す範囲が明示的には書けないことになりますから、範囲の終点としては指定できないと困ります。

また`a[0:3]`が`"abc"`、`a[1:3]`が`"bc"`、そして`a[2:3]`が`"c"`を表すのですから`a[3:3]`が空文字列を表すとするのは極めて自然だと思えないですか?
(ま、慣れるまでは思えないかもしれませんが…。)

と言うわけで(人間にとってと言うより機械の都合にとってと言う感じですが)`3`は範囲の始点を表す時、(必ず終点も同じでないといけませんが)有効な値ということになります。

[code]

    a[0:]
    |'a'|'b'|'c'|何もない
     [0]←ここから全部(`"abc"`)
    
    a[3:]
    |'a'|'b'|'c'|何もない
                 [3]←ここから全部(`""`)
    
[/code]

あなたのあげた例の場合は`len(a)==1`なのでかえってピンとこないかもしれませんがほとんど同じことです。

[code]

    a[:0] (==a[:len(a)]
    |'a'|何もない
         [1]←この1個前まで全部("a")
    
[/code]

[code]

    a[0:] (==a[len(a):]
    |'a'|何もない
         [1]←ここから全部("")
    
[/code]

* * *

なんでこんなわかりにくい範囲指定をするのかは、その方が色々便利だから、ですが、その辺はこのやり方で経験を積まれれば、自然に見えてくるかと思います。

それ他の点で何か分かりにくい点があれば、お知らせください。


CC BY-SA 4.0
2019-04-21T12:38:24.013
54365
2019-04-21T13:03:00.320
2019-04-21T13:03:00.320
13972
13972
54364
answer


回答ではありませんが、ソースコード上での処理はどの様になっているのか調べてみました。

スライスの処理は[ここ](https://go.googlesource.com/go/+/refs/heads/master/src/cmd/compile/internal/gc/ssa.go#4359)で行われています。

> slice computes the slice v[i:j:k] and returns ptr, len, and cap of result.
> **i,j,k may be nil, in which case they are set to their default value**.

コメントにあるように、`a[len(a):]` の場合は `a[len(a):len(a)]` として扱われて、境界チェック(bounds
checking)が行われます。この場合、`a` は文字列ですので、最終的に `s.boundsCheck(i, j, ssa.BoundsSliceB,
bounded)` が実行されることになります。

[code]

    case t.IsString():
      ptr = s.newValue1(ssa.OpStringPtr, types.NewPtr(types.Types[TUINT8]), v)
      len = s.newValue1(ssa.OpStringLen, types.Types[TINT], v)
      cap = len
          :
    
    // Set default values
          :
    if j == nil {
      j = len
    }
    three := true
    if k == nil {
      three = false
      k = cap
    }
    
    // Panic if slice indices are not in bounds.
          :
    if three {
          :        
    } else {
      if j != k {
          :        
      }
      i = s.boundsCheck(i, j, ssa.BoundsSliceB, bounded)
    }
    
[/code]

`ssa.BoundsSliceB`
は境界チェック用のオペレーション・コードですが、[ここ](https://go.googlesource.com/go/+/refs/heads/master/src/cmd/compile/internal/ssa/op.go#190)で定義されています。

[code]

    type BoundsKind uint8
    
    const (
      BoundsIndex    BoundsKind = iota // indexing operation, 0 <= idx < len failed
                   :
      BoundsSliceB     // 2-arg slicing operation, 0 <= low <= high failed
    
[/code]

判定条件は `0 <= low <= high` となっていて、`low`, `high` ともに `len(a)`
であるため境界内であると判定されることになります。

`a[len(a)+1:]` の場合は `low = len(a)+1`, `high = len(a)` となるので、`low > high`
となってしまい、境界チェックに引っ掛かってしまいます(`panic(bounds out of range)`が発生)。

仮に判定条件が `0 <= low < high` となっていたとすれば `a[len(a):]` でも panic が発生することになったはずです。

ちなみに、`a[len(a)]` の様なインデックスによるアクセスの場合には `BoundsIndex`
でチェックされますので、先に引用したコードにもある通り、`0 <= idx < len` が判定条件になります(`a[len(a)]` では `index
out of range` となる)。


CC BY-SA 4.0
2019-04-21T17:59:27.730
54374
2019-04-21T17:59:27.730
54364
answer