First, the blog post you linked and took your example from, appError
is not an error
. It's a wrapper that carriers 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
func New(text string) error {
return &errorString{text}
}
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).