Go Slice
Go - Slices
Section titled “Go - Slices”Slices are one of the most powerful and commonly used data structures in Go. They provide a more flexible and dynamic interface to sequences of data compared to arrays.
Think of a slice as a view or a window onto an underlying array. Unlike arrays, slices have a dynamic size – they can grow and shrink.
Slice Internals
Section titled “Slice Internals”A slice itself doesn’t store the data directly. It’s a small descriptor structure containing three fields:
Understanding this internal structure is key to understanding slice behavior, especially when slicing existing slices or using append.
Creating Slices
Section titled “Creating Slices”There are several ways to create slices:
- Slice Literal: Similar to array literals but without specifying the size.
// Creates an underlying array and returns a slice referencing itprimes := []int{2, 3, 5, 7, 11, 13} // len=6, cap=6
names := []string{"Go", "Python", "Java"} // len=3, cap=3
emptySlice := []int{} // len=0, cap=0 (but not nil)- Using the
makefunction: Creates a slice, allocating an underlying array of the specified size. Elements are initialized to their zero value.
// make([]T, length, capacity)numbers := make([]int, 5) // len=5, cap=5. Initializes with [0 0 0 0 0]
// Specify length and capacity explicitlydata := make([]byte, 3, 10) // len=3, cap=10. Initializes with [0 0 0]Using make is useful when you know the approximate size needed, helping to avoid multiple reallocations when appending.
- Slicing an Existing Array or Slice: Creates a new slice that references a portion of the original array or slice.
baseArray := [6]int{0, 1, 2, 3, 4, 5}
sliceA := baseArray[1:4] // Creates a slice from index 1 (inclusive) to 4 (exclusive) // sliceA contains [1 2 3], len=3, cap=5 (from index 1 to end of baseArray)
sliceB := sliceA[0:2] // Creates a slice from sliceA, index 0 to 2 // sliceB contains [1 2], len=2, cap=5 (still shares baseArray's capacity)
// sliceA and sliceB SHARE the same underlying array (baseArray)!// Modifying sliceB[0] will also change sliceA[0] and baseArray[1].- Declaring a Nil Slice: Declaring a slice variable without initialization results in a nil slice.
var nilSlice []int // nilSlice is nil, len=0, cap=0
fmt.Println(nilSlice == nil) // Output: trueNil slices are useful as initial values; many standard library functions handle them gracefully (e.g., append).
Length (len) and Capacity (cap)
Section titled “Length (len) and Capacity (cap)”The built-in len() function returns the number of elements in the slice (its length).
The built-in cap() function returns the capacity of the slice (the maximum length it can reach before needing reallocation).
package main
import "fmt"
func main() { s := make([]int, 3, 5) // length 3, capacity 5 printSliceInfo("s", s)
s = s[:cap(s)] // Extend length to capacity printSliceInfo("s extended", s)
s = s[1:] // Reduce length from the beginning printSliceInfo("s sliced", s)}
func printSliceInfo(name string, x []int) { fmt.Printf("%s: len=%d cap=%d slice=%v\n", name, len(x), cap(x), x)}Output:
s: len=3 cap=5 slice=[0 0 0]s extended: len=5 cap=5 slice=[0 0 0 0 0]s sliced: len=4 cap=4 slice=[0 0 0 0]Sub-slicing (slice[low:high])
Section titled “Sub-slicing (slice[low:high])”Slicing creates a new slice header that points to the same underlying array. Syntax: slice[low:high]
low: The starting index (inclusive). Defaults to 0 if omitted ([:high])
high: The ending index (exclusive). Defaults to len(slice) if omitted ([low:]).
The length of the new slice is high - low. The capacity is calculated from the low index of the original slice to the end of its underlying array’s capacity.
package main
import "fmt"
func main() { numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8} // len=9, cap=9 printSliceInfo("numbers", numbers)
// Sub-slice from index 1 (included) to 4 (excluded) fmt.Println("numbers[1:4] ==", numbers[1:4]) // [1 2 3] printSliceInfo("numbers[1:4]", numbers[1:4]) // len=3 cap=8
// Missing lower bound implies 0 fmt.Println("numbers[:3] ==", numbers[:3]) // [0 1 2] printSliceInfo("numbers[:3]", numbers[:3]) // len=3 cap=9
// Missing upper bound implies len(s) fmt.Println("numbers[4:] ==", numbers[4:]) // [4 5 6 7 8] printSliceInfo("numbers[4:]", numbers[4:]) // len=5 cap=5
// IMPORTANT: Modifications through sub-slices affect the original! sub := numbers[2:5] // [2 3 4], len=3, cap=7 sub[0] = 99 fmt.Println("After modifying sub[0]:") printSliceInfo("sub", sub) // sub: len=3 cap=7 slice=[99 3 4] printSliceInfo("numbers", numbers) // numbers: len=9 cap=9 slice=[0 1 99 3 4 5 6 7 8]}
func printSliceInfo(name string, x []int) { fmt.Printf("%s: len=%d cap=%d slice=%v\n", name, len(x), cap(x), x)}Output:
numbers: len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]numbers[1:4] == [1 2 3]numbers[1:4]: len=3 cap=8 slice=[1 2 3]numbers[:3] == [0 1 2]numbers[:3]: len=3 cap=9 slice=[0 1 2]numbers[4:] == [4 5 6 7 8]numbers[4:]: len=5 cap=5 slice=[4 5 6 7 8]After modifying sub[0]:sub: len=3 cap=7 slice=[99 3 4]numbers: len=9 cap=9 slice=[0 1 99 3 4 5 6 7 8]Appending to a Slice (append)
Section titled “Appending to a Slice (append)”The built-in append function adds elements to the end of a slice. It’s crucial to understand how it works with capacity:
Crucially, you must always assign the result of append back to a slice variable, as it might return a new slice pointing to a different underlying array.
slice = append(slice, element1, element2, ...)slice = append(slice, anotherSlice...)package main
import "fmt"
func main() { var numbers []int // Start with a nil slice printSliceInfo("numbers (initial)", numbers)
// Append works on nil slices numbers = append(numbers, 0) printSliceInfo("numbers (appended 0)", numbers)
// Add one more element numbers = append(numbers, 1) printSliceInfo("numbers (appended 1)", numbers)
// Add multiple elements at once // This might trigger a reallocation if capacity is exceeded numbers = append(numbers, 2, 3, 4) printSliceInfo("numbers (appended 2,3,4)", numbers)
// Append another slice using ... moreNumbers := []int{5, 6} numbers = append(numbers, moreNumbers...) printSliceInfo("numbers (appended moreNumbers)", numbers)}
func printSliceInfo(name string, x []int) { fmt.Printf("%s: len=%d cap=%d slice=%v\n", name, len(x), cap(x), x)}Output (Capacity might vary slightly depending on Go version’s growth strategy):
numbers (initial): len=0 cap=0 slice=[]numbers (appended 0): len=1 cap=1 slice=[0]numbers (appended 1): len=2 cap=2 slice=[0 1]numbers (appended 2,3,4): len=5 cap=6 slice=[0 1 2 3 4]numbers (appended moreNumbers): len=7 cap=12 slice=[0 1 2 3 4 5 6]Copying Slices (copy)
Section titled “Copying Slices (copy)”The built-in copy function copies elements from a source slice (src) to a destination slice (dst). It returns the number of elements copied.
numCopied := copy(dst, src)copy will copy min(len(dst), len(src)) elements. It copies element by element from source to destination and does not automatically grow the destination slice. The source and destination may overlap.
package main
import "fmt"
func main() { source := []int{1, 2, 3, 4} dest1 := make([]int, 2) // Destination smaller than source num1 := copy(dest1, source) fmt.Printf("Copied %d elements to dest1: %v\n", num1, dest1) // Output: Copied 2 elements to dest1: [1 2]
dest2 := make([]int, 5) // Destination larger than source num2 := copy(dest2, source) fmt.Printf("Copied %d elements to dest2: %v\n", num2, dest2) // Output: Copied 4 elements to dest2: [1 2 3 4 0]
// To create an independent copy, make a destination of the same size independentCopy := make([]int, len(source)) copy(independentCopy, source) independentCopy[0] = 99 // Modify the copy fmt.Println("Source:", source) // Output: Source: [1 2 3 4] fmt.Println("Independent Copy:", independentCopy) // Output: Independent Copy: [99 2 3 4]}Slices are fundamental to idiomatic Go programming, offering a balance of performance and flexibility for managing sequences of data.