Preferred return type, pointer or value, when the method/function can also return an error

softwareengineering.stackexchange https://softwareengineering.stackexchange.com/questions/404474

  •  07-03-2021
  •  | 
  •  

Question

In Go we can choose to make something a pointer or not. We can also support multiple return types. A common signature on functions or methods is:

type MyType struct{}

// Value Return Type
func (m MyType) FindOne(key string)(MyType, error) {...}

// Pointer Return Type
func (m MyType) FindOne(key string)(*MyType, error) {...}

In reviewing the repos on Github (non-exhaustive) for signatures like the above, it seems to be preferred to use the pointer style return type when something can also return an error. Is that the general consensus?

Few Examples (again non-exhaustive I have definitely not seen everything there is to see):

https://github.com/etcd-io/etcd/blob/9c5426830b1b8728af14e069acfdc2a64dea768c/clientv3/kv.go#L48-L54

https://github.com/go-redis/redis/blob/d19aba07b47683ef19378c4a4d43959672b7cec8/cluster.go#L329

https://github.com/bradfitz/gomemcache/blob/master/memcache/memcache.go#L317

In thinking about it, and here's where my question lies, I would think the pointer style would be preferred on calls where a result might not exist:

service := MyType{}
result, err := service.FindOne("not-found-key")

In this way result can be nil and only the err has a value. Effectively preventing anyone from operating result. Whereas in the value type return, if nothing is found, we would still need to return an empty MyType{}. Assuming we are good Go citizens, we would naturally error check things always. Nothing is perfect and things slip by, so in the case where someone were to do something like this:

service := MyType{}
result, _ := service.FindOne("not-found-key")

// do stuff with `result` because of course it will ALWAYS succeed
// we don't need to error check (until we do)

The execution will continue even though we are now in a potentially unknown state. Where as is we tried to do things to that pointer return value, since it's nil, it would panic and we'd know about it right away.

This has additional implications as well in the design of our MyType service. To keep things consistent, if we introduced at FindMany method:

type MyType struct{}

func (m MyType) FindOne(key string)(*MyType, error) {...}
func (m MyType) FindMany(key string)([]*MyType, error) {...}

We would probably benefit from keeping the Pointer return types consistent so callers don't have to think too much as it's always the same either a Pointer or Value of MyType.

Was it helpful?

Solution

I don't think it particularly matters which you use, at least not relating to error handling. The whole point of returning an err along with a result is so that you can test for an error before accessing the return result. So ultimately it shouldn't matter if your return a valid , because if you are properly checking the error result first, an invalid would never be referenced.

Licensed under: CC-BY-SA with attribution
scroll top