Cory LaNou
Tue, 01 Aug 2023

The Slices Package

Overview

In release 1.21, the slices package will be officially added to the standard library. It includes many useful functions for sorting, managing, and searching slices. In this article, we will cover the more commonly used functions included in the Slices package.

Target Audience

This article is aimed at developers that have minimum experience with Go.

In this article, we'll cover the following topics:

  • Introduce the Slices package that is released in Go 1.21
  • A quick introduction to the following functions:
    • Clip
    • Clone
    • Compact
    • Contains
    • Index
    • Insert
    • Max
    • Min
    • Replace
    • Sort
  • Finally, we'll end with an example that puts many of these functions together to solve real world scenarios.

Clip

Clip removes unused capacity from the slice. This can be used to release memory back to the runtime if you have had a large amount if items in a slice and have since reduced the length (or in use items).

fmt.Printf("len: %d, capacity: %d\n", len(ints), cap(ints))
ints = slices.Clip(ints)
fmt.Printf("len: %d, capacity: %d\n", len(ints), cap(ints))
$ go run .

len: 25, capacity: 500
len: 25, capacity: 25

--------------------------------------------------------------------------------
Go Version: go1.22.0

Clone

Clone returns a copy of the slice. All values are copied by assignment, which means that this is considered a shallow copy of elements in the slice. A shallow copy means that the original memory addresses are still in use, so it's possible you might still make changes to the original data. While this normally isn't a problem, it's important to keep in mind if you want a truly independent copy of the data.

original := []int{0, 1, 2, 4, 5}
cloned := slices.Clone(original)
cloned[0] = 9
cloned[1] = 8
cloned[2] = 7
cloned[3] = 6
cloned[4] = 5
fmt.Printf("original: %v\n", original)
fmt.Printf("cloned  : %v\n", cloned)
$ go run .

original: [0 1 2 4 5]
cloned  : [9 8 7 6 5]

--------------------------------------------------------------------------------
Go Version: go1.22.0

Clone with Sub Slices

Clone is especially useful when taking a subslice of a slice. Using slices.Clone instead of directly slicing will allow you to mutate (or make changes) to the subslice without directly affecting the parent slice you copied the data from.

Take the following example. If you take a subslice of the parent, and make changes to the sliced elements, it will also make changes to the parent slice as well:

original := []int{0, 1, 2, 4, 5}
cloned := original[1:4]
cloned[0] = 9
cloned[1] = 8
cloned[2] = 7
fmt.Printf("original: %v\n", original)
fmt.Printf("cloned  : %v\n", cloned)
$ go run .

original: [0 9 8 7 5]
cloned  : [9 8 7]

--------------------------------------------------------------------------------
Go Version: go1.22.0

To avoid this, you can use the slices.Clone function and wrap the subslice to make a copy of the data instead of a reference to the data. This allows us to make changes to the sliced data without affecting the original data.

original := []int{0, 1, 2, 4, 5}
cloned := slices.Clone(original[1:4]) // <-- clone the subslice
cloned[0] = 9
cloned[1] = 8
cloned[2] = 7
fmt.Printf("original: %v\n", original)
fmt.Printf("cloned  : %v\n", cloned)
$ go run .

original: [0 1 2 4 5]
cloned  : [9 8 7]

--------------------------------------------------------------------------------
Go Version: go1.22.0

Notice that the parent slice is no longer affected by the changes made by the subslice.

Compact

Compact replaces consecutive runs of equal elements with a single copy.

The documentation doesn't state that the slice already needs to be sorted for the function to work. Upon a quick test, it shows that compact does in fact require a sorted slice to work properly. Luckily, the slices package made sorting very easy as well.

strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 1, 2, 0, 5, 7, 5}
strings = slices.Compact(strings)
ints = slices.Compact(ints)
fmt.Printf("Not Sorted - Compact does not work\n")
fmt.Println(strings)
fmt.Println(ints)

fmt.Printf("\nSorted - Compact now works as expected\n")
slices.Sort(ints)
slices.Sort(strings)
strings = slices.Compact(strings)
ints = slices.Compact(ints)
fmt.Println(strings)
fmt.Println(ints)
$ go run .

Not Sorted - Compact does not work
[Apple Orange Apple Banana]
[0 1 2 0 5 7 5]

Sorted - Compact now works as expected
[Apple Banana Orange]
[0 1 2 5 7]

--------------------------------------------------------------------------------
Go Version: go1.22.0

Contains

Contains reports if the provided value exists in the slice. This is useful to detect the presence of an element in a slice.

s := []string{"Apple", "Banana", "Orange"}
fmt.Println("Found Apple?", slices.Contains(s, "Apple"))
fmt.Println("Found Banana?", slices.Contains(s, "Banana"))
fmt.Println("Found Strawberry?", slices.Contains(s, "Strawberry"))
$ go run .

Found Apple? true
Found Banana? true
Found Strawberry? false

--------------------------------------------------------------------------------
Go Version: go1.22.0

Index

Index returns the index of the first occurrence of the searched value in the slice. It will return -1 if the value is not present.

s := []string{"Apple", "Banana", "Orange"}
fmt.Println("Index for Apple:      ", slices.Index(s, "Apple"))
fmt.Println("Index for Banana:     ", slices.Index(s, "Banana"))
fmt.Println("Index for Strawberry: ", slices.Index(s, "Strawberry"))
$ go run .

Index for Apple:       0
Index for Banana:      1
Index for Strawberry:  -1

--------------------------------------------------------------------------------
Go Version: go1.22.0

Insert

Insert inserts the value at the index provided. The slice will grow as needed to make room for the inserted values.

s := []string{"Apple", "Banana", "Orange"}
s = slices.Insert(s, 2, "Grape")
fmt.Println(s)
$ go run .

[Apple Banana Grape Orange]

--------------------------------------------------------------------------------
Go Version: go1.22.0

Max

Max returns the max value in the slice. It will panic if the slice is empty. The slice does NOT need to be sorted for this function to work.

strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 9, 1, 2, 0, 5, 7, 5}
fmt.Println("Max value for strings slice is", slices.Max(strings))
fmt.Println("Max value for ints slice is", slices.Max(ints))
$ go run .

Max value for strings slice is Orange
Max value for ints slice is 9

--------------------------------------------------------------------------------
Go Version: go1.22.0

Min

Min returns the minimal value in the slice. It will panic if the slice is empty. The slice does NOT need to be sorted for this function to work.

strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 9, 1, 2, 0, 5, 7, 5}
fmt.Println("Min value for strings slice is", slices.Min(strings))
fmt.Println("Min value for ints slice is", slices.Min(ints))
$ go run .

Min value for strings slice is Apple
Min value for ints slice is 0

--------------------------------------------------------------------------------
Go Version: go1.22.0

Replace

Replace replaces the selected section of the slice with the provided values.

Note: You do not need to replace the with the exact number of elements. You can use less elements or more elements as necessary. This would allow you to replace 2 elements with 1 element, or 1 element with 3 elements, etc.

strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 9, 1, 2, 0, 5, 7, 5}

strings = slices.Replace(strings, 0, 2, "Apple", "Grape")
ints = slices.Replace(ints, 0, 2, 9, 9)

fmt.Println(strings)
fmt.Println(ints)

// Replace and insert at the same time
strings = slices.Replace(strings, 0, 0, "Prune", "Pineapple")
ints = slices.Replace(ints, 0, 0, -1, -1)

fmt.Println(strings)
fmt.Println(ints)
$ go run .

[Apple Grape Apple Banana]
[9 9 9 1 2 0 5 7 5]
[Prune Pineapple Apple Grape Apple Banana]
[-1 -1 9 9 9 1 2 0 5 7 5]

--------------------------------------------------------------------------------
Go Version: go1.22.0

Sort

Sort sorts a slice of any ordered type in ascending order. Probably one of the most useful functions added to the standard library since Go was released in 2012!

strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 1, 2, 0, 5, 7, 5}
fmt.Println(strings)
fmt.Println(ints)

// Sort strings
slices.Sort(ints)
slices.Sort(strings)

fmt.Println(strings)
fmt.Println(ints)
$ go run .

[Apple Orange Apple Banana]
[0 1 1 2 0 5 7 5]
[Apple Apple Banana Orange]
[0 0 1 1 2 5 5 7]

--------------------------------------------------------------------------------
Go Version: go1.22.0

Putting It Together

Below, I'll show an example of something that previously would have required a lot of code (and somewhat hard to read and understand as well).

For this example, we will take a slice that has duplicate items and:

  1. Replace all occurrences of "snake" with "gopher"
  2. Sort the slice
  3. Compact the slice
  4. Clip the slice to free up any space (not necessary on a slice this size, but using it to show it would be useful on a larger scale operation).

Note: If this wasn't an example, I would have sorted and compacted the slice first to decrease the amount of work that the Replace function had to do. But I wanted to make this a fun example to show off the slices package!

animals := []string{
	"snake",
	"guinea pig",
	"elephant",
	"deer",
	"snake",
	"dung beetle",
	"okapi",
	"badger",
	"lovebird",
	"coyote",
	"camel",
	"antelope",
	"marten",
	"rabbit",
	"cougar",
	"cow",
	"finch",
	"snake",
	"reindeer",
	"rat",
	"moose",
	"crocodile",
	"snake",
	"giraffe",
	"gnu",
}
fmt.Printf("Original Slice:\nlen: %d, \ncap: %d, \nelements: %v\n", len(animals), cap(animals), animals)

i := slices.Index(animals, "snake")
for i > -1 {
	animals = slices.Replace(animals, i, i+1, "gopher")
	i = slices.Index(animals, "snake")
}

slices.Sort(animals)

animals = slices.Compact(animals)

animals = slices.Clip(animals)

fmt.Printf("Final Slice:\nlen: %d, \ncap: %d, \nelements: %v\n", len(animals), cap(animals), animals)
$ go run .

Original Slice:
len: 25, 
cap: 25, 
elements: [snake guinea pig elephant deer snake dung beetle okapi badger lovebird coyote camel antelope marten rabbit cougar cow finch snake reindeer rat moose crocodile snake giraffe gnu]
Final Slice:
len: 22, 
cap: 22, 
elements: [antelope badger camel cougar cow coyote crocodile deer dung beetle elephant finch giraffe gnu gopher guinea pig lovebird marten moose okapi rabbit rat reindeer]

--------------------------------------------------------------------------------
Go Version: go1.22.0

Summary

As you can see, the slices package adds many useful functions that most Go developers will be using daily.

Want More?

If you've enjoyed reading this article, you may find these articles interesting as well:

More Articles

Hype Quick Start Guide

Overview

This article covers the basics of quickly writing a technical article using Hype.

Learn more

Writing Technical Articles using Hype

Overview

Creating technical articles can be painful when they include code samples and output from running programs. Hype makes this easy to not only create those articles, but ensure that all included content for the code, etc stays up to date. In this article, we will show how to set up Hype locally, create hooks for live reloading and compiling of your documents, as well as show how to dynamically include code and output directly to your documents.

Learn more

Go (golang) Slog Package

Overview

In Go (golang) release 1.21, the slog package will be added to the standard library. It includes many useful features such as structured logging as well as level logging. In this article, we will talk about the history of logging in Go, the challenges faced, and how the new slog package will help address those challenges.

Learn more

Subscribe to our newsletter