In Go, slices are a flexible and dynamic data structure built on top of arrays. Unlike arrays, which have a fixed size, slices can grow and shrink as needed.
Slices are widely used in Go because of their flexibility and ease of use.
In this tutorial, we’ll cover:
1. Declaring and Initializing Slices
Slices can be declared in multiple ways, including creating an empty slice, initializing a slice with values, or using the make function.
Declaring an Empty Slice
package main import "fmt" func main() { var nums []int // Declare an empty slice of integers fmt.Println("Empty slice:", nums) }
Output:
Empty slice: []
In this example:
- nums is an empty slice of integers. Since no elements are assigned, it has a length of 0.
Initializing a Slice with Values
package main import "fmt" func main() { colors := []string{"red", "green", "blue"} fmt.Println("Colors:", colors) }
Output:
Colors: [red green blue]
In this example:
- colors is a slice of strings initialized with values.
Using the make Function
The make function can be used to create a slice with a specified length and capacity.
package main import "fmt" func main() { nums := make([]int, 3) // Creates a slice of length 3 with zero values fmt.Println("Nums slice:", nums) nums[0] = 10 nums[1] = 20 nums[2] = 30 fmt.Println("Updated nums slice:", nums) }
Output:
Nums slice: [0 0 0] Updated nums slice: [10 20 30]
In this example:
- nums is initialized with make([]int, 3), creating a slice of length 3 with default values (0 for integers).
2. Appending Elements to Slices
Slices can be expanded by using the append function. The append function creates a new slice with the added elements, and may allocate a new underlying array if needed.
package main import "fmt" func main() { fruits := []string{"apple", "banana"} fruits = append(fruits, "cherry") // Append a single element fmt.Println("Fruits:", fruits) moreFruits := []string{"mango", "orange"} fruits = append(fruits, moreFruits...) // Append another slice fmt.Println("Updated fruits:", fruits) }
Output:
Fruits: [apple banana cherry] Updated fruits: [apple banana cherry mango orange]
In this example:
- append(fruits, “cherry”) adds “cherry” to fruits.
- append(fruits, moreFruits…) adds all elements from moreFruits to fruits using the … spread operator.
3. Slicing Slices
A slice can be sliced further using slice[start:end], which creates a sub-slice from start to end-1.
package main import "fmt" func main() { numbers := []int{10, 20, 30, 40, 50} slice1 := numbers[1:4] // Get elements from index 1 to 3 fmt.Println("Slice 1:", slice1) slice2 := numbers[:3] // Get elements from the start to index 2 fmt.Println("Slice 2:", slice2) slice3 := numbers[2:] // Get elements from index 2 to the end fmt.Println("Slice 3:", slice3) }
Output:
Slice 1: [20 30 40] Slice 2: [10 20 30] Slice 3: [30 40 50]
In this example:
- numbers[1:4] extracts elements from index 1 to 3.
- numbers[:3] gets elements from the start to index 2.
- numbers[2:] gets elements from index 2 to the end.
Note: Slicing creates a new view of the underlying array. Changes in the slice affect the original array.
4. Working with len and cap Functions
The len function returns the length of a slice, while cap returns its capacity (the maximum size the slice can reach without allocating a new array).
package main import "fmt" func main() { nums := []int{1, 2, 3, 4, 5} fmt.Println("Length:", len(nums)) // Length is 5 fmt.Println("Capacity:", cap(nums)) // Capacity is also 5 subSlice := nums[1:4] fmt.Println("Sub-slice length:", len(subSlice)) // Length is 3 fmt.Println("Sub-slice capacity:", cap(subSlice)) // Capacity is 4 }
Output:
Length: 5 Capacity: 5 Sub-slice length: 3 Sub-slice capacity: 4
In this example:
- len(nums) gives the length of nums.
- cap(nums) gives the capacity of nums.
- subSlice is created from nums[1:4]. Its length is 3, but it shares the same underlying array, giving it a capacity of 4.
5. Copying Slices
To copy elements from one slice to another, use the copy function. The destination slice must be initialized with enough capacity to hold the elements.
package main import "fmt" func main() { src := []int{1, 2, 3} dst := make([]int, len(src)) // Create a destination slice with the same length copy(dst, src) fmt.Println("Source slice:", src) fmt.Println("Destination slice after copy:", dst) }
Output:
Source slice: [1 2 3] Destination slice after copy: [1 2 3]
In this example:
- copy(dst, src) copies elements from src to dst.
- dst is pre-allocated with the same length as src to ensure it can hold all elements.
6. Best Practices with Slices
a) Use append to Add Elements
Use the append function to dynamically grow slices rather than manually managing the size.
numbers := []int{1, 2, 3} numbers = append(numbers, 4)
b) Be Cautious with Slice References
Since slices are references to the underlying array, modifications to a slice can affect the original array or other slices derived from it.
package main import "fmt" func main() { data := []int{1, 2, 3, 4, 5} subData := data[1:4] subData[0] = 99 // This modifies both subData and data fmt.Println("Original array:", data) fmt.Println("Sub-slice:", subData) }
Output:
Original array: [1 99 3 4 5] Sub-slice: [99 3 4]
c) Pre-Allocate Slices When Possible
If you know the size of a slice in advance, use make to pre-allocate memory to avoid reallocations and improve performance.
package main import "fmt" func main() { nums := make([]int, 0, 10) // Pre-allocated with capacity 10 nums = append(nums, 1, 2, 3) fmt.Println("Slice with pre-allocated capacity:", nums) }
Output:
Slice with pre-allocated capacity: [1 2 3]
d) Use copy for Independent Slices
When you need a completely separate copy of a slice, use copy to avoid modifying the original slice.
Summary
In this tutorial, we covered the basics of slices in Go:
- Declaring and Initializing Slices: Creating slices with and without initial values.
- Appending Elements to Slices: Using append to add elements dynamically.
- Slicing Slices: Creating sub-slices from existing slices.
- Working with len and cap Functions: Using len and cap to check length and capacity.
- Copying Slices: Using copy to duplicate slice contents.
- Best Practices with Slices: Writing efficient, idiomatic code with slices.
Slices are a powerful feature in Go that provide flexibility and dynamic memory management. Understanding how to work with slices effectively is essential for writing idiomatic and efficient Go code.