Slicing in Golang: Understanding the Basics
Tags: go, programming, learning
Published: 2023-05-16 03:19:24

 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
	*/
}

```
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.