当base64编码字节数组时,为什么会出现“索引超出范围”错误?

When encoding a byte array into a base64 byte array, the below code produces a runtime index out of range error. How can this be resolved?

package main

import (
    "fmt"
    "encoding/base64"
)

func main() {
    data := []byte("string of data")
    var encodedData []byte
    base64.StdEncoding.Encode(encodedData, data)
    fmt.Println(encodedData)
}

Playground here

The error is:

panic: runtime error: index out of range

goroutine 1 [running]:
encoding/base64.(*Encoding).Encode(0xc420056000, 0x0, 0x0, 0x0, 0xc42003bf30, 0xe, 0x20)
        /usr/lib/go/src/encoding/base64/base64.go:113 +0x27b
main.main()
        /home/martin/a.go:11 +0x9b
exit status 2

shell returned 1

If we look at /usr/lib/go/src/encoding/base64/base64.go line 113, we see (abbreviated):

n := (len(src) / 3) * 3
for si < n {
    // [..]
    dst[di+0] = enc.encode[val>>18&0x3F]
    dst[di+0] = enc.encode[val>>18&0x3F]
    dst[di+1] = enc.encode[val>>12&0x3F]
    // [..]
}

In other words, this function directly sets the index of dst. The length of var encodedData []byte is zero, so you get the index out of range error.

One way to fix it would be to change the line to:

encodedData := make([]byte, base64.StdEncoding.EncodedLen(len(data)))

Which will make an array of the size in the second argument. Base64 encoded data is larger than the input, hence the base64.StdEncoding.EncodedLen().

However, this is hardly the best way to fix it. The documentation mentions:

Encode is not appropriate for use on individual blocks of a large data stream. Use NewEncoder() instead.

Rewriting the code to NewEncoder() looks like:

func main() {
    data := []byte("string of data")

    encodedData := &bytes.Buffer{}
    encoder := base64.NewEncoder(base64.StdEncoding, encodedData)
    defer encoder.Close()
    encoder.Write(data)

    fmt.Println(encodedData)
}

There also some other useful functions, such as EncodeToString() which make the above a bit shorter and more convenient: encodedData := base64.StdEncoding.EncodeToString(data).

Allocate space for the output. For example,

package main

import (
    "encoding/base64"
    "fmt"
)

func main() {
    data := []byte("string of data")
    encodedData := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
    base64.StdEncoding.Encode(encodedData, data)
    fmt.Println(encodedData)
}

Output:

[99 51 82 121 97 87 53 110 73 71 57 109 73 71 82 104 100 71 69 61]

var encodedData []byte is zero bytes. Therefore, index out of range

Because Encode expects the dst slice to be allocated to the correct length.

Encode encodes src using the encoding enc, writing EncodedLen(len(src)) bytes to dst.