Returning slice of pointers vs pointer to slice

Hi,

I want to return []Category.
But I am confused which one is better option?

func GetCategories() (*[]Category, error) {}
func GetCategories() ([]*Category, error) {}

Between this which one does go follows?

Is it okay to do something like this?
func GetCategories() (*[]*Category, error) {} //have not tried this.

There’s not much point to returning a pointer to a slice.

So return it.
Always have a reason to pass pointer. You should default to not use pointers.

There is

Would you care to elaborate? I can’t think of any case where returning a pointer to a slice is a good idea. It would let you distinguish between p == nil vs. *p == nil, but I would be interested in seeing the code that uses the result. I imaging there would be a better way to do that (e.g. return a pointer to a struct with a single slice field, maybe?).

However, I do agree with you that you should have a reason to use a pointer and if you don’t know if you do or not, then you do not need a pointer.

1 Like

Agreed. I can see passing a pointer to an array, but not a slice as it holds a reference to an underlying array anyway:

Slices hold references to an underlying array, and if you assign one slice to another, both refer to the same array. If a function takes a slice argument, changes it makes to the elements of the slice will be visible to the caller, analogous to passing a pointer to the underlying array.

1 Like

Categories is a struct with many fields. So instead of passing the data, i thought passing the addresses via a slice will be faster.

So, we should use pointer only if we need to change the original data. Otherwise we can just send the copy of the data. Am i right?

If you pass or return a single Category, then there will be a copy of all the fields. If you put a Category in a slice, then there will be a copy of all the fields at that time. If you pass or return a slice of Categories, then there will be no additional copies of Category data. If you pass or return pointer to a Category, then there are no copies (other than the 8 bytes for the pointer), but the Category referred to by the pointer may be modified from two bits of code and that may cause bugs. Putting a pointer in a slice will also not copy the Category, and you can get similar sharing. Returning a slice of Categories vs a slice of Category pointers has no difference in the quantity of data copied. Returning a slice allows possible sharing of the Categories or pointers in the slice because the data backing the slice is visible to all copies of the slice. There’s little point to returning a pointer to a slice other than copying a pointer means copying 8 bytes and copying a slice means copying 24 bytes. Perhaps one could use unsafe access to modify a slice’s internal data, but that is highly irregular. (Byte counts may be different in different environments, but the point is that copying a slice is only slightly more expensive than copying a pointer.)

3 Likes

I will add that returning copies of data is pretty efficient for the most part. Unless Category has a giant chunk of binary data (like a video or something), I doubt you will run into a problem in the real world. So, this is probably premature optimization. Your bottleneck will almost certainly be in another layer (such as data access, doing a bunch of joins to return the large chunk of category data in the first place).

3 Likes

Galdly!

https://go.dev/play/p/cxu64Jn6XNQ

package main

import "fmt"

type ErrorsAccumulator []error

func (ea *ErrorsAccumulator) Add(err error) {
	*ea = append(*ea, err)
}

func NewEA() *ErrorsAccumulator {
	ea := make(ErrorsAccumulator, 0)
	return &ea
}
func main() {
	ea := NewEA()
	ea.Add(fmt.Errorf("ee"))

	fmt.Println(ea, *ea)
}

While a bit contrived, the example tries to show that shared state might be represented just as a slice, and taking a pointer on that would make sense like taking a point on any struct.

Would I reccomend this? No. But I stay away of pointers regradless to what they point to. And I do prefer to wrap slices and maps in a struct.

So How I would actually do the abocve example is

package main

import "fmt"

type ErrorsAccumulator struct {
	errs []error
}

func (ea *ErrorsAccumulator) Add(err error) {
	(*ea).errs = append((*ea).errs, err)
}

func NewEA() *ErrorsAccumulator {
	return &ErrorsAccumulator{errs: make(ErrorsAccumulator, 0)}
}
...
2 Likes

Ah, I hadn’t considered a named slice type. Touché!

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.