pass values
Don't pass pointers as function arguments just to save a few bytes. If a
function refers to its argument x
only as *x
throughout, then the argument
shouldn't be a pointer. Common instances of this include passing a pointer to a
string (*string) or a pointer to an interface value (*io.Reader). In both cases
the value itself is a fixed size and can be passed directly. This advice does
not apply to large structs, or even small structs that might grow.
replacing the passed value
Instead of c = &tree
you need to do *c = tree
. Otherwise you're simply
updating the functions local copy of the container that holds the address, not
the actual address from the caller.
type Comments []Comment
func processComments(c *Comments) {
comments := *c
var tree Comments
for i := range comments {
if comments[i].ParentID == nil {
tree = append(tree, comments[i])
continue
}
}
*c = tree
}
pointer methods
If a function or method takes a pointer it should be mutating the variable. This is usually what idiomatic code uses pointers for. There are of course exceptions, such as an optimization to avoid copying around a massive value constantly, but go will generally internally pass a reference anyway if it knows you will not modify it.
pointer returns
https://philpearl.github.io/post/bad_go_pointer_returns/
Basically, returning a pointer to a struct is slower unless that struct will be long lived. This is because of some of the extra garbage collection that comes with allocating a pointer.
slices of pointers
Things like []*MyStruct
. Unless you need to express that certain indices in
the slice are nil, then this is just wasteful and []MyStruct
is better in
almost all circumstances. This is because slices are already references to data.
Adding another layer on top slows things down. Here's some benchmarks:
type MyStruct struct {
A int
B int
}
func BenchmarkSlicePointers(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
slice := make([]*MyStruct, 0, 100)
for j := 0; j < 100; j++ {
slice = append(slice, &MyStruct{A: j, B: j + 1})
}
}
}
func BenchmarkSliceNoPointers(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
slice := make([]MyStruct, 0, 100)
for j := 0; j < 100; j++ {
slice = append(slice, MyStruct{A: j, B: j + 1})
}
}
}
$ go test -bench . -count 10 > run1.txt
$ benchstat run1.txt
name time/op
SlicePointers-8 2.50µs ± 2%
SliceNoPointers-8 117ns ± 1%
name alloc/op
SlicePointers-8 1.60kB ± 0%
SliceNoPointers-8 0.00B
name allocs/op
SlicePointers-8 100 ± 0%
SliceNoPointers-8 0.00