If you look at "encoding/binary" package:
func (littleEndian) Uint64(b []byte) uint64 {
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
}
func (littleEndian) PutUint64(b []byte, v uint64) {
_ = b[7] // early bounds check to guarantee safety of writes below
b[0] = byte(v)
b[1] = byte(v >> 8)
b[2] = byte(v >> 16)
b[3] = byte(v >> 24)
b[4] = byte(v >> 32)
b[5] = byte(v >> 40)
b[6] = byte(v >> 48)
b[7] = byte(v >> 56)
}
You will see:
_ = b[7] // early bounds check to guarantee safety of writes below
Now consider this sample code A (see comment):
package main
import "fmt"
func main() {
b := []byte{0, 1, 2, 3, 4, 5, 6}
var v uint64 = 0x0807060504030201
b[0] = byte(v)
b[1] = byte(v >> 8)
b[2] = byte(v >> 16)
b[3] = byte(v >> 24)
b[4] = byte(v >> 32)
b[5] = byte(v >> 40)
b[6] = byte(v >> 48)
b[7] = byte(v >> 56) // panic: runtime error: index out of range
fmt.Println(b)
}
And this sample code B (see comment):
package main
import "fmt"
func main() {
b := []byte{0, 1, 2, 3, 4, 5, 6}
var v uint64 = 0x0807060504030201
b[7] = byte(v >> 56) // panic: runtime error: index out of range
b[6] = byte(v >> 48)
b[5] = byte(v >> 40)
b[4] = byte(v >> 32)
b[3] = byte(v >> 24)
b[2] = byte(v >> 16)
b[1] = byte(v >> 8)
b[0] = byte(v)
fmt.Println(b)
}
And sample code C:
package main
import "fmt"
func main() {
b := []byte{0, 1, 2, 3, 4, 5, 6}
var v uint64 = 0x0807060504030201
_ = b[7] // early bounds check to guarantee safety of writes below
b[0] = byte(v)
b[1] = byte(v >> 8)
b[2] = byte(v >> 16)
b[3] = byte(v >> 24)
b[4] = byte(v >> 32)
b[5] = byte(v >> 40)
b[6] = byte(v >> 48)
b[7] = byte(v >> 56)
fmt.Println(b)
}
So I have two questions:
Q1: Is it necessary to early bounds check to guarantee safety of writes in Golang?
Q2: For early bounds check to guarantee safety of writes which Sample Code is more concise and performance optimized (speed), sample code A, B, C or ... ?
A2: I think B : because it is concise and do early bounds check , isn't it?
Q1: is it necessary to early bounds check to guarantee safety of writes in Golang?
The answer here is "yes and no". In general, "no", you do not normally have to insert bounds checks in Go because the compiler inserts them for you (that's why your examples panic when you try to access a memory location beyond the length of the slice). However, if you're doing multiple writes like the example given then, "yes", you'll need to insert an early bounds check like the example provided to ensure that you don't have only some of the writes succeed, putting you in a bad state (or refactor as you did in example B so that the first write is to the largest array, ensuring that the panic will happen before any writes can succeed).
However, this isn't so much a "go problem" as it is a generic class of bug. If you don't do bounds checking (or don't start with the highest index if it's a language that enforces bounds checking itself like Go) in any language, the writes aren't safe. It also very heavily depends on the situation; in the example from the standard library you posted, the user bounds check is necessary. In the second example you posted, however, the user bounds check is not necessary because the code can be written like B where the compiler inserts the bounds check on the first line.
Q2: for early bounds check to guarantee safety of writes which Sample Code is more concise and performance optimized (speed), sample code A, B, C or ... ?
A2: I think B : because it is concise and do early bounds check , isn't it?
You are correct. In B the compiler will insert a bounds check on the first write, protecting the rest of the writes. Because you are indexing the slice with a constant (7
, 6
, … 0
) the compiler can elide the bounds check from the rest of the writes since it can guarantee that they are safe.
The comment about "safety of writes" is misleading here. Placing the highest bounds check at the start is simply an optimisation. If you omit it, the behavior will not change (or become "unsafe") but you may suffer the performance penalty of multiple bounds checks instead of just one, as the minimum required bound increments with each subsequent higher index.
Where the comment says "guarantee safety of writes" it just means that it will guarantee to the compiler that the subsequent writes will be safe without needing to insert more bounds checks. Leaving it out won't make the writes unsafe, it'll just make the compiler insert more bounds checks. Under no conditions will the compiler generate unsafe memory accesses.
It is debatable whether inserting this fake early bounds check is a good idea in code as opposed to not using it or rewriting the code to legitimately use the highest index first as in your example code B. As long as it's clear why it's there (eg. with a sensible and non-misleading comment) I'd say use it if you want and find it beneficial. In general with manual optimisations there is the possibility that a future compiler optimisation may make it redundant or otherwise change its effectiveness.