Thanks to Julia Evan’s latest post, I learned that creating new slices by sub-slicing an existing slice has an important caveat: They can sometimes use the same backing array! π¬
This is important to understand if you mutate the sub-slice.
Take the below example, wherein we accidentally mutate s1
!
package main
import (
"fmt"
)
func main() {
s1 := []int{0, 1, 2, 3, 4, 5} // len == 6, capacity == 6
s2 := s1[1:5] // len == 4, capacity == 5
// Modifies both s1 and s2, because they share the same backing array.
s2[0] = 10
// Modifies both s1 and s2, because the length of s2 after adding the new
// element does not exceed the capacity of the s2 slice.
s2 = append(s2, 20)
// Modifies only s2, because adding this element increases the length of s2
// beyond the capacity of the s2 slice. This allocats a new backing array,
// copies all elements to it, and then appends 30 to it. So s1 does not get
// modified.
s2 = append(s2, 30)
fmt.Println("s1", s1)
fmt.Println("s2", s2)
}
Its output is:
s1 [0 10 2 3 4 20]
s2 [10 2 3 4 20 30]
Contrast this with the below example, where we do not mutate s1
.
package main
import (
"fmt"
)
func main() {
s1 := []int{0, 1, 2, 3, 4, 5}
// Copy the sub-slice values into a slice which uses a new backing array.
var s2 []int
s2 = append(s2, s1[1:5]...)
// None of the below change the value of s1 because s2 uses a different
// backing array.
s2[0] = 10
s2 = append(s2, 20)
s2 = append(s2, 30)
fmt.Println("s1", s1)
fmt.Println("s2", s2)
}
s1 [0 1 2 3 4 5]
s2 [10 2 3 4 20 30]