Let's look at the type definition of the slice header from the Go source code.
type slice struct {
array unsafe.Pointer
len int
cap int
}
The name of the "array" field is slightly misleading, as it's not a reference to an array type like you would get from a statement a := [10]int{}; p := &a, but rather a pointer to a specific element within an array (the element which is the first element of the slice).
Now, if you look at the unsafe package documentation, you might notice that incrementing an unsafe.Pointer into memory that is not allocated is an invalid operation:
It is valid both to add and to subtract offsets from a pointer in this way. It is also valid to use &^ to round pointers, usually for alignment. In all cases, the result must continue to point into the original allocated object.
Unlike in C, it is not valid to advance a pointer just beyond the end of its original allocation...
Despite this, there is no guarantee that this will actually be checked at runtime, so it's often perfectly possible and trivial to end up with an unsafe.Pointer that points to unallocated memory, as in the following example:
package main
import (
"fmt"
"unsafe"
)
func main() {
a := [10]int{}
for i := range a {
a[i] = i + 1
}
fmt.Printf("a: %v\n", a)
s := a[2:5]
p := unsafe.Pointer(&s[0])
fmt.Printf("p initially points at the 3rd element of a...\n")
// Let's break the rules!
for i := 0; i < 15; i++ {
if i == len(s) {
fmt.Printf("We've hit the end of slice s, but there is still memory allocated past this in the backing array a...\n")
}
if i == cap(s) {
fmt.Printf("We're in invalid/undefined territory now...\n")
}
fmt.Printf("addr: %v val: %v\n", uintptr(p), *(*int)(p))
p = unsafe.Pointer(uintptr(p) + unsafe.Sizeof(s[0]))
}
}
https://play.golang.org/p/4vF-7z45PP5
So, what does all this mean for extending a slice? Lets say you reslice a slice s like so:
t = s[a:b]
Then calculating the new slice header for t is trivial, given the header for s:
t.array = unsafe.Pointer(uintptr(s.array) + a * unsafe.Sizeof(s[0]))
t.len = b - a
t.cap = s.cap - a
Notably, this will work for both negative and non-negative values of a and b, assuming the backing array has the capacity.
More importantly, validating that t doesn't extend beyond it's allocated capacity is also trivial, given the above:
if t.len < 0 || t.len > t.cap {
panic("invalid slice!")
}
Unfortunately, the above assumption only holds if a and b are non-negative. There is no way to check if a re-slice has extended beyond the allocation of its backing array if negative slice indices are allowed, given the type definition of the slice header above. Because of this, negative indices are unsafe under the current slice implementation and not allowed.
That being said, it would be pretty simple to also track the capacity available in front of the pointer by adding another int field to the slice header, and then you could very easily check to ensure the slice is within its allocation for negative indices, but as of now the language authors haven't deemed it necessary.