We just got done speaking on arrays and it’s a perfect time to start talking about slice. The more versatile versions of arrays. This is because we don’t have to declare a size to a slice. Slices can grow indefinitely! But wait! There’s more! 😲 They also have certain global functions specific to them! And to top it all off they come with a really nice optimization for strings.

Also feel free to checkout the community driven Slice Tricks ! And if you want more input from one of the creators of Go, Rob Pike, check out the Go Blog on it! We’ll take this one slice 🍕 at a time 😹

Setup

Let’s make our directory slice and the files we want inside of that directory example_test.go slice.go

mkdir slice
touch slice/example_test.go slice/slice.go

Now let’s open up slice.go and for the very first line we’ll add

package slice

Next for example_test.go for the very first line we’ll add

package slice_test

We can import basics/slice into cmd/main.go and run functions from there with go run cmd/main.go and also to run our example_test.go 👍 we use go test slice/example_test.go in the commandline.

Initialize

Initializing (making) a slice is easier than an array, because we don’t have to think about how many elements we want in it, we just declare it.

Coding Time!

https://twitter.com/egonelbre

slice.go

// Basic shows how to create a slice and how to set and get values in it.
func Basic() {
	// Don't add a number between the
	// `[]` brackets and we `make` slices if we
	// want to have a capacity and length
	slice := make([]string, 3)
	fmt.Println("empty:", slice)

	slice[0] = "|set zeroeth value|"
	slice[1] = "|set first value|"
	slice[2] = "|set second value|"
	fmt.Println("full:", slice)
	fmt.Println("pick a value:", slice[2])
	fmt.Println("capacity:", cap(slice))
	fmt.Println("length:", len(slice))

	inline := []int{0, 1, 2, 3, 4}
	fmt.Println("Can be declared inline", inline)
}

example_test.go

func ExampleBasic() {
	slice.Basic()
	// Output:
	// empty: [  ]
	// full: [|set zeroeth value| |set first value| |set second value|]
	// pick a value: |set second value|
	// capacity: 3
	// length: 3
	// Can be declared inline [0 1 2 3 4]
}

Append

The magic 🌈 of slices. The append builtin function allows us to take our slice and append (add to the end) one or many values. While this is a very nice feature. We need to understand that everything comes with a cost.

Imagine if your fridge doubled in size if you over filled it❗ You might damage your roof or your walls 🏠 And now you are spending twice the amount of money 💲 on electricity 🔌 but you do have a fridge that doubles in size…. Which is still pretty cool 😀

Just know, if you know how big you need your slice to be ahead of time, you should set that size. Just like in life if you know how much material you need, you’ll get that much.

Coding Time!

https://twitter.com/egonelbre

slice.go

Here we introduce the unpacking operator ... which is further explained in the lesson on functions So, don’t worry we’ll get to it!

// Append shows how to put more elements into a slice even if we don't
// have the capacity for it using `append`.
func Append() {
	// Why wouldn't I do this always?
	var slice []string
	// Good Question! Lets answer it!
	fmt.Println("capacity:", cap(slice))
	fmt.Println("length:", len(slice))

	slice = append(slice, "append a single value")
	slice = append(slice, "append", "multiple", "values")
	fmt.Println("capacity:", cap(slice))
	fmt.Println("length:", len(slice))
	fmt.Println("We had to go find more space! Which takes time and effort!")
	fmt.Println("slice:", slice)

	unpackAllThese := []string{"`...`", "is used to put", "all the values in", "at the same time"}
	slice = append(slice, unpackAllThese...)
	fmt.Println("capacity:", cap(slice))
	fmt.Println("length:", len(slice))
	fmt.Println("We had to go find even more space!!!")
	fmt.Println("slice:", slice)
}

example_test.go

func ExampleAppend() {
	slice.Append()
	// Output:
	// capacity: 0
	// length: 0
	// capacity: 4
	// length: 4
	// We had to go find more space! Which takes time and effort!
	// slice: [append a single value append multiple values]
	// capacity: 8
	// length: 8
	// We had to go find even more space!!!
	// slice: [append a single value append multiple values `...` is used to put all the values in at the same time]
}

Copy

There are times when you need to copy values of one slice to another, without changing the original slice. I won’t go into it, but in general, we love 😍 immutability in programming. We love ❤️ when things can’t change, because if they can’t change we don’t have to worry about them. The copy builtin function, does what it says it does, and allows us to imitate immutability. The source slice is kept intact, while the destination slice receives a copy of the values.

Coding Time!

https://twitter.com/egonelbre

slice.go

// Copy shows how to copy one slice into another slice using the builtin
// `copy` function.
func Copy() {
	// src is short for source
	srcSlice := make([]int, 10)
	fmt.Println("empty srcSlice:", srcSlice)
	for i := 0; i < 10; i++ {
		srcSlice[i] = i
	}
	fmt.Println("full srcSlice:", srcSlice)

	// dst is short for destination
	dstSlice := make([]int, len(srcSlice))
	fmt.Println("empty dstSlice:", dstSlice)
	copy(dstSlice, srcSlice)
	fmt.Println("full dstSlice:", dstSlice)
}

example_test.go

func ExampleCopy() {
	slice.Copy()
	// Output:
	// empty srcSlice: [0 0 0 0 0 0 0 0 0 0]
	// full srcSlice: [0 1 2 3 4 5 6 7 8 9]
	// empty dstSlice: [0 0 0 0 0 0 0 0 0 0]
	// full dstSlice: [0 1 2 3 4 5 6 7 8 9]
}

Panic! At the Slice

We will cover panic in Go in a later lesson, but this could be your first time running into it! I know it was mine 😂 That is, let’s ask a question: What happens if you try and access an index that isn’t in the slice? 🤔

Well let’s go back to our hotel analogy from the last lesson on arrays. If I was a guest in room 2, but I accidentally went into room 3, I’d get in trouble. I’ve gone somewhere that I shouldn’t have access to!

Same thing happens with both arrays and slices. If you try and access memory outside of the capacity, you’re going to have a bad time ⏰

Coding Time!

https://twitter.com/egonelbre

slice.go

I don’t like showing off things we haven’t learned yet, but later we will understand what panic, recover, and defer are doing. For now copy and paste and come back later after learning about panic, recover and defer.

// IndexOutOfRangePanic shows us what happens when we try to access an
// index that does not exist in a slice.
func IndexOutOfRangePanic() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("slice paniced!\n", r)
		}
	}()

	sl := make([]int, 5)
	// Change -1 to 0 to see the panic happen at the other end of the slice.
	for i := -1; i < len(sl)+1; i++ {
		fmt.Println("NOTE(jay): this is going to panic before we ever see this!", sl[i])
	}
}

example_test.go

func ExampleIndexOutOfRangePanic() {
	slice.IndexOutOfRangePanic()
	// Output:
	// slice paniced!
	//  runtime error: index out of range [-1]
}

What’s with the name?

So all the way up until now, we’ve been calling it a slice. Slice this, slice that, slice with certain len, slice with certain cap. But why slice? If you come from other programming languages you may be familiar with Lists. These are, in fact, very similar to Lists in other languages, the biggest feature and why they are called slice is because of how efficient the slice operation with the slice operator : is. What efficiency you may ask? Well I’ll let Rob Pike speak on it.

An important consequence of this slice-like design for strings is that creating a substring is very efficient. All that needs to happen is the creation of a two-word string header. Since the string is read-only, the original string and the string resulting from the slice operation can share the same array safely. – Rob Pike

Coding Time!

https://twitter.com/egonelbre

slice.go

// ReasonForName shows us why a slice is called a slice and that's because we can
// take slices (pieces) of a slice depending on our needs using the `:` slice
// operator.
func ReasonForName() {
	var slice = []string{"zero", "one", "two", "three", "four", "five"}
	fmt.Printf("sliceUpToThirdIndex: %v\nlength: %d capacity: %d\n",
		slice,
		len(slice),
		cap(slice))

	sliceUpToThirdIndex := slice[:3]
	fmt.Printf("sliceUpToThirdIndex: %v\nlength: %d capacity: %d\n",
		sliceUpToThirdIndex,
		len(sliceUpToThirdIndex),
		cap(sliceUpToThirdIndex))

	sliceStartAtIndexTwo := slice[2:]
	fmt.Printf("sliceStartAtIndexTwo: %v\nlength: %d capacity: %d\n",
		sliceStartAtIndexTwo,
		len(sliceStartAtIndexTwo),
		cap(sliceStartAtIndexTwo))

	sliceFromOneUpToFour := slice[1:4]
	fmt.Printf("sliceFromOneUpToFour: %v\nlength: %d capacity: %d\n",
		sliceFromOneUpToFour,
		len(sliceFromOneUpToFour),
		cap(sliceFromOneUpToFour))
	s := "Max Efficiency"
	fmt.Println(s[4:], "to the", s[:3], "for substrings")
}

example_test.go

func ExampleReasonForName() {
	slice.ReasonForName()
	// Output:
	// sliceUpToThirdIndex: [zero one two three four five]
	// length: 6 capacity: 6
	// sliceUpToThirdIndex: [zero one two]
	// length: 3 capacity: 6
	// sliceStartAtIndexTwo: [two three four five]
	// length: 4 capacity: 4
	// sliceFromOneUpToFour: [one two three]
	// length: 3 capacity: 5
	// Efficiency to the Max for substrings
}

Matrix 😎

It’s time ⏲️ to enter – SYKE! – if you were following along from last lesson on arrays you knew this was coming, if not: A matrix is an array of arrays or in this case a slice of slices! Why the fancy term? Well try saying slice of slices over and over, it’s just too extra. Now we can grow our slices in our growable slice 🤯. It’s the exact same as array of arrays, but you can add more if you want. So don’t be intimidated, you got this! 💪😤

Coding Time!

https://twitter.com/egonelbre

slice.go

// Matrix shows how to make a matrix also known as a 2d array, but still
// have the flexibility of slices!
func Matrix() {
	// We will allocate three slices in a slice
	matrix := make([][]int, 3)
	fmt.Println("matrix empty:", matrix)
	for i := 0; i < 3; i++ {
		innerLen := i + 1
		matrix[i] = make([]int, innerLen)
		for j := 0; j < innerLen; j++ {
			matrix[i][j] = i + j
		}
	}
	fmt.Println("matrix full:", matrix)

	// and we can treat each slice like we would any other slice.
	matrix[0] = append(matrix[0], 21)
	fmt.Println("matrix append first slice with value:", matrix)
}

example_test.go

func ExampleMatrix() {
	slice.Matrix()
	// Output:
	// matrix empty: [[] [] []]
	// matrix full: [[0] [1 2] [2 3 4]]
	// matrix append first slice with value: [[0 21] [1 2] [2 3 4]]
}

Source File 📄

The Source File

Test File 📝

The Test File