DEV Community

Cover image for Slicing in Golang: Understanding the Basics
Adrian DY
Adrian DY

Posted on • Edited on

Slicing in Golang: Understanding the Basics

Slicing is an essential concept in Golang that allows you to take a portion of a slice without actually copying the underlying data. In this article, we'll explore what slicing is, how it works, and how it can be used to manipulate data. We’ll also dive into an important facet of slicing: how changing the capacity of a slice can affect its sub-slices.

What is Slicing in Golang?
A slice in Golang is a dynamically-sized array. It's created using the make() built-in function and has a type that includes the element type and the size of the array. However, unlike an array, a slice is dynamic; its size can be changed at run-time.
Slicing is the process of taking a portion of a slice and creating a new slice from it. The new slice refers to the same underlying array as the original slice, which means that any changes made to the new slice will be reflected in the original slice.

Taking a Sub-Slice in Golang
To take a sub-slice of a slice, you can use the slice operator. This operator has two parameters: the start index and the end index of the sub-slice. The start index is inclusive, while the end index is exclusive.

Let's take a look at an example:
slicen1 := []int{1, 2, 3, 4, 5}
subSlicen := slicen1[1:3]

In this example, we take a sub-slice of slicen1 from the 1st index to the 3rd index (exclusive). The resulting sub-slice is {2, 3} .

Sub-Slices and Changing Capacity
When you create a sub-slice in Golang, it refers to a portion of the underlying array of the original slice. This means that any changes made to the sub-slice will be reflected in the original slice.
However, there is an important caveat to keep in mind: if you append a new value to the original slice that causes its capacity to increase, it will no longer refer to the same underlying array, and the sub-slice will lose its reference.
Let's look at the code example:

package main

import "fmt"

func main() {
    numbers := []int{1, 2, 3}
    subNumbers := numbers[:2]
    fmt.Println("numbers: ", numbers)
    fmt.Printf("Len: %d, Capac: %v, Memory Ref: %p \n", len(numbers), cap(numbers), numbers)
    fmt.Println("subNumbers: ", subNumbers)
    fmt.Printf("Sub - Len: %d, Capac: %v, Memory Ref: %p \n", len(subNumbers), cap(subNumbers), subNumbers)
    numbers[1] = 10
    fmt.Println("numbers[1] = 10")
    fmt.Println("numbers: ", numbers)
    fmt.Println("subNumbers: ", subNumbers)
    numbers = append(numbers, 4)
    fmt.Println("append(numbers, 4)")
    fmt.Println("numbers: ", numbers)
    fmt.Printf("Len: %d, Capac: %v, Memory Ref: %p \n", len(numbers), cap(numbers), numbers)
    fmt.Println("subNumbers: ", subNumbers)
    fmt.Printf("Sub - Len: %d, Capac: %v, Memory Ref: %p \n", len(subNumbers), cap(subNumbers), subNumbers)
    numbers[1] = 20
    fmt.Println("numbers[1] = 20")
    fmt.Println("numbers: ", numbers)
    fmt.Printf("Len: %d, Capac: %v, Memory Ref: %p \n", len(numbers), cap(numbers), numbers)
    fmt.Println("subNumbers: ", subNumbers)
    fmt.Printf("Sub - Len: %d, Capac: %v, Memory Ref: %p \n", len(subNumbers), cap(subNumbers), subNumbers)
    numbers = append(numbers, 5)
    fmt.Printf("Len: %d, Capac: %v, Memory Ref: %p \n", len(numbers), cap(numbers), numbers)
    numbers = append(numbers, 6)
    fmt.Printf("Len: %d, Capac: %v, Memory Ref: %p \n", len(numbers), cap(numbers), numbers)
    numbers = append(numbers, 7)
    fmt.Printf("Len: %d, Capac: %v, Memory Ref: %p \n", len(numbers), cap(numbers), numbers)

    /*
       Output:
        numbers:  [1 2 3]
        Len: 3, Capac: 3, Memory Ref: 0xc00001c1c8 
        subNumbers:  [1 2]
        Sub - Len: 2, Capac: 3, Memory Ref: 0xc00001c1c8 
        numbers[1] = 10
        numbers:  [1 10 3]
        subNumbers:  [1 10]
        append(numbers, 4)
        numbers:  [1 10 3 4]
        Len: 4, Capac: 6, Memory Ref: 0xc000022210 
        subNumbers:  [1 10]
        Sub - Len: 2, Capac: 3, Memory Ref: 0xc00001c1c8 
        numbers[1] = 20
        numbers:  [1 20 3 4]
        Len: 4, Capac: 6, Memory Ref: 0xc000022210 
        subNumbers:  [1 10]
        Sub - Len: 2, Capac: 3, Memory Ref: 0xc00001c1c8 
        Len: 5, Capac: 6, Memory Ref: 0xc000022210 
        Len: 6, Capac: 6, Memory Ref: 0xc000022210 
        Len: 7, Capac: 12, Memory Ref: 0xc00001a120
    */
}

Enter fullscreen mode Exit fullscreen mode

In this example, we create a slice called numbers with three elements. We create a sub-slice called subNumbers that refers to the first two elements of numbers .
Next, we append an element to numbers and modify its second element's value. As you can see from the output, these changes are reflected in both numbers and subNumbers .
However, when we append another element to numbers , its capacity increases to six. Since the sub-slice subNumbers had a capacity of three, it loses its reference to the underlying array and refers to a new array that's created when the numbers slice is appended to.

Conclusion
In summary, slicing is an essential concept in Golang that allows you to take a portion of a slice without copying the underlying data. When you take a sub-slice of a slice, any changes made to the sub-slice are reflected in the original slice. However, if you append to the original slice and its capacity increases, it will lose its reference to its underlying array, and any sub-slices created from it will no longer reflect changes made to the original slice.

Top comments (0)