kota's memex

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