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?
Pointer receiver passes the address of a type to the function. The function stack has a reference to the original object. So any modifications on the passed object will modify the original object.
Creating custom errors using the New function. Adding more information to the error using Errorf. Providing more information about the error using struct type and fields. Providing more information about the error using methods on struct types.
error is a built-in interface type in Go. An error variable represents any value that can describe itself as a string . The interface consists of a single function, Error() , that returns a string error message.
In summary, here's the gist of what was covered here: Errors in Go are just lightweight pieces of data that implement the Error interface. Predefined errors will improve signaling, allowing us to check which error occurred. Wrap errors to add enough context to trace through function calls (similar to a stack trace)
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With