How to get a segmentation violation in Go
Today I learned the important difference between "declaring" variables in Go with a starting value, and just "initializing" them. Turns out its important. Who knew?
The problem
When working with the AWS SDK for Go I was trying to list off a group of EKS clusters and do some work on each cluster. Pretty basic task. My first attempt at solving this looked something like this and simply got all the clusters from the AWS SDK (I have the listing function wrapped inside the listClusters
function on the aws
package) and it then appends on the clusters until there is no nextToken
present. To give a hint of the issue I hit, take a look at line 2.
var nextToken *string = nil
var finalListClustersOutput *eks.ListClustersOutput
keepLooping := true
for keepLooping {
input := &eks.ListClustersInput{
NextToken: nextToken,
}
listClustersOutput, _ := aws.listClusters(eksClient, input)
finalListClustersOutput.Clusters = append(finalListClustersOutput.Clusters, listClustersOutput.Clusters...)
nextToken = listClustersOutput.NextToken
keepLooping = nextToken != nil
}
On running this however I hit the dreaded Segmentation Violation error which was certainly not was I was expecting for such a simple bit of code.
Running tool: /usr/local/go/bin/go test -timeout 30s -run ^TestFunction$ path/to/function/being/tested
--- FAIL: TestFunction (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1]
Hooking up a debugger to this code stack and inspecting the finalListClustersOutput
variable showed that it was indeed nil
.
finalListClustersOutput: *github.com/aws/aws-sdk-go/service/eks.ListClustersOutput nil
The fix
Even though I've been writing Go for a bit now there are still occasions when something in the basics jumps out and bites me. This was one of them.
var finalListClustersOutput *eks.ListClustersOutput
What this line does is initialize the variable finalListClustersOutput
but does so with the type being a pointer to EKS ListClustersOutput, and then critically initializes it to its zero value which in our case is, you guessed it, nil
. If you do this with an int
type variable you'll get a starting value of 0
for example. I spent a lot of time writing Python and JS before my current gig so some part of my brain thought when I initialized a variable this way it defined the type and then....well I don't know did magic or something to let me add values to it, something like creating an empty list of clusters. Not so. As a result on the first .append
pass the code would try to append to nil
and thus panic.
The solution is luckily very simple.
var nextToken *string = nil
var clusterNameList []*string
keepLooping := true
for keepLooping {
input := &eks.ListClustersInput{
NextToken: nextToken,
}
listClustersOutput, _ := aws.listClustersWrapper(eksClient, input)
clusterNameList = append(clusterNameList, listClustersOutput.Clusters...)
nextToken = listClustersOutput.NextToken
keepLooping = nextToken != nil
}
Now there is a proper list of strings to append values to and the code runs as expected. An alternative is to create a pointer to an instance instead of a nil pointer, something like this
finalListClustersOutput := &eks.ListClustersOutput{}
Happy appending!