Home Go Tutorial on Slices in Go

Tutorial on Slices in Go

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:

  1. Declaring and Initializing Slices: Creating slices with and without initial values.
  2. Appending Elements to Slices: Using append to add elements dynamically.
  3. Slicing Slices: Creating sub-slices from existing slices.
  4. Working with len and cap Functions: Using len and cap to check length and capacity.
  5. Copying Slices: Using copy to duplicate slice contents.
  6. 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.

You may also like