Running any code for the first time may have some (significant) overhead, e.g. related code may be loaded, many things may be deferred until they are needed (e.g. internal buffers). Running the same thing again may take significantly less time, the difference may even be several orders of magnitude.

Whenever you want to measure execution times, you should run it many times, measure the execution time of the multiple runs, and calculate average time. It's also a good idea to exclude the first (some) runs from the calculation for the above mentioned reasons.

In Go, best and easiest is to use test files and benchmark functions. Read the package doc of testing for more details and examples.

Your case can be benchmarked like this:

package main

import "testing"

type abc struct {
    a, b, c, d int
}

func BenchmarkSlice(b *testing.B) {
    a := make([]int, 4)
    for i := 0; i < b.N; i++ {
        a[0] = 1
        a[1] = 2
        a[2] = 3
        a[3] = 4
    }
}

func BenchmarkStruct(b *testing.B) {
    a := abc{}
    for i := 0; i < b.N; i++ {
        a.a = 1
        a.b = 2
        a.c = 3
        a.d = 4
    }
}

Save it to a file like something_test.go, run it with go test -bench .. Output:

BenchmarkSlice-4        2000000000           1.24 ns/op
BenchmarkStruct-4       2000000000           0.31 ns/op

You can see that using a struct is roughly 4 times faster. You will get similar (very close) results if you reorder the benchmark functions.


The other answer explained the timing difference, let's get into struct vs. slice.

If the compiler can figure out at compile time that the slice is big enough, accessing the elements of the slice and a struct will generate identical code. Of course, in reality, often the compiler won't know how big the slice is and completely different optimizations will be applied depending on if you're working with a struct or a slice, so for measuring performance you have to look at a whole program and its behavior, not just one particular operation.