26

Reading about value receivers vs pointer receivers across the web and stackoverflow, I understand the basic rule to be: If you don't plan to modify the receiver, and the receiver is relatively small, there is no need for pointers.

Then, reading about implementing the error interface (eg. https://blog.golang.org/error-handling-and-go), I see that examples of the Error() function all use pointer receiver.

Yet, we are not modifying the receiver, and the struct is very small.

I feel like the code is much nicer without pointers (return &appError{} vs return appError{}).

Is there a reason why the examples are using pointers?

icza
  • 389,944
  • 63
  • 907
  • 827
Nathan H
  • 48,033
  • 60
  • 165
  • 247
  • 3
    No there is not. Maybe just personal style or consistency. Sometimes you _must_ use a pointer receiver. If your case does not fall in the "must" section: Choose whatever you like or makes sense. – Volker May 14 '18 at 15:05

5 Answers5

60

First, the blog post you linked and took your example from, appError is not an error. It's a wrapper that carries an error value and other related info used by the implementation of the examples, they are not exposed, and not appError nor *appError is ever used as an error value.

So the example you quoted has nothing to do with your actual question. But to answer the question in title:

In general, consistency may be the reason. If a type has many methods and some need pointer receiver (e.g. because they modify the value), often it's useful to declare all methods with pointer receiver, so there's no confusion about the method sets of the type and the pointer type.

Answering regarding error implementations: when you use a struct value to implement an error value, it's dangerous to use a non-pointer to implement the error interface. Why is it so?

Because error is an interface. And interface values are comparable. And they are compared by comparing the values they wrap. And you get different comparison result based what values / types are wrapped inside them! Because if you store pointers in them, the error values will be equal if they store the same pointer. And if you store non-pointers (structs) in them, they are equal if the struct values are equal.

To elaborate on this and show an example:

The standard library has an errors package. You can create error values from string values using the errors.New() function. If you look at its implementation (errors/errors.go), it's simple:

// Package errors implements functions to manipulate errors.
package errors

// New returns an error that formats as the given text.
func New(text string) error {
    return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

The implementation returns a pointer to a very simple struct value. This is so that if you create 2 error values with the same string value, they won't be equal:

e1 := errors.New("hey")
e2 := errors.New("hey")
fmt.Println(e1, e2, e1 == e2)

Output:

hey hey false

This is intentional.

Now if you would return a non-pointer:

func New(text string) error {
    return errorString{text}
}

type errorString struct {
    s string
}

func (e errorString) Error() string {
    return e.s
}

2 error values with the same string would be equal:

e1 = New("hey")
e2 = New("hey")
fmt.Println(e1, e2, e1 == e2)

Output:

hey hey true

Try the examples on the Go Playground.

A shining example why this is important: Look at the error value stored in the variable io.EOF:

var EOF = errors.New("EOF")

It is expected that io.Reader implementations return this specific error value to signal end of input. So you can peacefully compare the error returned by Reader.Read() to io.EOF to tell if end of input is reached. You can be sure that if they occasionally return custom errors, they will never be equal to io.EOF, this is what errors.New() guarantees (because it returns a pointer to an unexported struct value).

icza
  • 389,944
  • 63
  • 907
  • 827
  • Great detailed response. A co-worker thought, if we use non-pointer, we wouldn't be able to do the usual `if err != nil` - is that right? – Nathan H May 15 '18 at 07:05
  • @NathanH No, that's not correct. `nil` is not a valid value for structs, that's true, but if you work with `error` values (which may wrap your non-pointer struct), `error` is an interface type which may have a value of `nil` (that's the zero value for interface types). But I recommend to read this answer to clear things up: [Hiding nil values, understanding why golang fails here](https://stackoverflow.com/questions/29138591/hiding-nil-values-understanding-why-golang-fails-here/29138676#29138676). – icza May 15 '18 at 07:12
  • 1
    PG. 196 of the GOPL (Donovan,Kernighan) make the same point, but this answer explains it in detail. In each cases return pointer or struct, New creates a distinct instances of errorString. It is the value returned by new, an error interface,that is the problem. When a struct is returned the two instances of the error interface compare equal since they have the same values, even though they were created from 2 distinct errorStruts. When a pointer is ret'd, the two ints of error are distinct, since the pointer refers to the two separate insts of errorString (created by each call to New). – drlolly Aug 31 '18 at 08:31
  • I think "... often it's useful to declare all methods with pointer receiver," could be rectified to say "it may be the case that correctness depends on consistency" and that mixing the methods of a type to have both pointer and non-pointer receivers may have undesirable side-effects. Here's an [example](https://play.golang.org/p/AHrc55sZcq9). – Kedar Mhaswade Jan 27 '19 at 13:55
  • Second sentence of answer should be updated to read "...carries an error..." instead of "...carriers an error..." – Bill Morgan Jan 08 '22 at 23:07
  • 1
    In the first snippet copied from the `errors` package, func `New` has the following signature `func New(text string) error`. I'm coming from a C background and noticed that the function returns `&errorString{text}`, but the return type in the signature is not `*error`, it's `error`. To me, this type discrepancy seems like it should throw a compile-time error, but it obviously does not. Can anyone explain what is going on there? – E. Pratt Jan 24 '22 at 05:45
  • 3
    @E.Pratt `error` is an interface type, `&errorString{}` is a value of concrete type (`*errorString`), and this pointer type implements the `error` interface. When this value is returned, an `error` interface value is created. Any value whose type implements `error` can be assigned to a variable of `error` type, or can be returned when the return type is `error`. – icza Jan 24 '22 at 08:09
3

Errors in go only satisfy the error interface, i.e. provide a .Error() method. Creating custom errors, or digging through Go source code, you will find errors to be much more behind the scenes. If a struct is being populated in your application, to avoid making copies in memory it is more efficient to pass it as a pointer. Furthermore, as illustrated in The Go Programming Language book:

The fmt.Errorf function formats an error message using fmt.Sprintf and returns a new error value. We use it to build descriptive errors by successively prefixing additional context information to the original error message. When the error is ultimately handled by the program’s main function, it should provide a clear causal chain from the root problem to the overall failure, reminiscent of a NASA accident investigation:

genesis: crashed: no parachute: G-switch failed: bad relay orientation

Because error messages are frequently chained together, message strings should not be capitalized and newlines should be avoided. The resulting errors may be long, but they will be self-contained when found by tools like grep.

From this we can see that if a single 'error type' holds a wealth of information, and on top of this we are 'chaining' them together to create a detailed message, using pointers will be the best way to achieve this.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
brun
  • 133
  • 9
3

We can look at this from the error handling's perspective, instead of the error creation.

Error Definiton Side's Story

type ErrType1 struct {}

func (e *ErrType1) Error() string {
    return "ErrType1"
}

type ErrType2 struct {}

func (e ErrType2) Error() string {
    return "ErrType1"
}

Error Handler Side's Story

err :=  someFunc()
switch err.(type) {
case *ErrType1
   ...
case ErrType2, *ErrType2
   ...
default
   ...
}

As you can see, if you implements a error type on a value receiver, then when you are doing the type assertion, you need to worry about both cases.

For ErrType2, both &ErrType2{} and ErrType2{} satisfy the interface.

Because someFunc returns an error interface, you never know if it returns a struct value or a struct pointer, especially when someFunc isn't written by you.

Therefore, by using a pointer receiver doesn't stop a user from returning a pointer as an error.

That been said, all other aspects such as Stack vs. Heap (memory allocation, GC pressure) still apply.

Choose your implementation according to your use cases.

In general, I prefer to a pointer receiver for the reason I demonstrated above. I prefer to Friendly API over performance and sometimes, when error type contains huge information, it's more performant.

Albert X.W.
  • 183
  • 1
  • 3
  • 8
1

No :)

https://blog.golang.org/error-handling-and-go#TOC_2.

Go interfaces allow for anything that complies with the error interface to be handled by code expecting error

type error interface {
    Error() string
}

Like you mentioned, If you don't plan to modify state there is little incentive to pass around pointers:

  • allocating to heap
  • GC pressure
  • Mutable state and concurrency, etc

On a random rant , Anecdotally, I personally think that seeing examples like this one are why new go programers favor pointer receivers by default.

dm03514
  • 54,664
  • 18
  • 108
  • 145
  • 1
    Incidentally, if your structure is intended to be used inside an interface, the default should be a _value receiver_ anyway. Pointer receivers can only fulfill interfaces if a pointer is passed in to the interface. Value receivers work for either pointers or values. [Details](https://stackoverflow.com/questions/44370277/type-is-pointer-to-interface-not-interface-confusion/44372954#44372954) – Kaedys May 14 '18 at 15:07
  • @Kaedys I think it is the other way around, if I understand right your point. From the SO piece you mention as Details: _"the default rule of thumb is to store pointers to structures in interfaces, unless there's a compelling reason not to"_. – Picci Oct 26 '22 at 18:37
  • The rule of thumb is to store _a pointer_ in the interface. That's _because_ if you store a non-pointer in an interface, only _value_ methods are accessible (and only value methods can be used to fulfill the interface). If the _method_ is a value method, however, it works regardless. Hence my recommendation to make the method a value method unless there's a good reason to make it a pointer. – Kaedys Jan 26 '23 at 09:21
-1

The tour of go explains the general reasons for pointer receivers pretty well:

https://tour.golang.org/methods/8

There are two reasons to use a pointer receiver.

The first is so that the method can modify the value that its receiver points to.

In general, all methods on a given type should have either value or pointer receivers, but not a mixture of both.

Community
  • 1
  • 1
klayman
  • 101