Just started using Golang. I think that it is idiomatic to declare an error variable and use it in your error structure to determine what went wrong, as is done in strconv.go. There, ErrRange
and ErrSyntax
is declared, and when appropriate, references to those are stored in NumError
structs when they return. I think that the reason is because then the address of the reference to the error stored in NumError
can be compared with the ErrRange
and ErrSyntax
variables to determine which type of error was returned.
Are there "standard" such declared error types? In Java, for example, you have things like java.lang.IllegalArgumentException
. Is there, for instance, ErrArgument
or ErrUnsupportedOperation
that I can use in my own code instead of creating new error variables that mean the same thing every time?
The idiomatic way of handling errors in Go is to compare the returned error to nil . A nil value indicates that no error has occurred and a non-nil value indicates the presence of an error. In our case, we check whether the error is not nil in line no.
The easiest way to create custom errors in Golang is to use the New() function which is present in the errors module of Golang.
Errors can be returned as nil , and in fact, it's the default, or “zero”, value of on error in Go. This is important since checking if err != nil is the idiomatic way to determine if an error was encountered (replacing the try / catch statements you may be familiar with in other programming languages).
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.
There are a few common idiomatic ways for a package author to make error returns.
Fixed error variables, usually named Err…
var (
ErrSomethingBad = errors.New("some string")
ErrKindFoo = errors.New("foo happened")
)
Error types, usually named …Error
type SomeError struct {
// extra information, whatever might be useful to callers
// (or for making a nice message in `Error()`)
ExtraInfo int
}
type OtherError string
func (e SomeError) Error() string { /* … */ }
func (e OtherError) Error() string {
return fmt.Sprintf("failure doing something with %q", string(e))
}
With Go 1.13 and later you may also want to implement a Unwrap() error
method for use with errors.Unwrap
.
Ad hoc errors.New
values as needed.
func SomepackageFunction() error {
return errors.New("not implemented")
}
Using errors defined in the standard packages. Usually limited to a small set such as io.EOF
; in most cases it's better to create your own via method 1 above.
func SomeFunc() error {
return io.EOF
}
Note that sometimes when implementing an interface (such as a Read
method to become an io.Reader
) it is best to use matching errors (or "required" by the specification of the interface).
Making an interface such as net.Error
:
type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}
With Go 1.13 or later, returning an existing error with simple context (for more complicated context, use a custom error type with an Unwrap()
method):
func SomepackageFunction() error {
err := somethingThatCanFail()
if err != nil {
return fmt.Errorf("some context: %w", err)
}
}
Note the new (to Go 1.13) formatting verb %w
, it wraps the provided error so that callers can get at it with errors.Unwrap
or error.Is
.
Often you'll use a mix of all these ways.
The first, second, and fifth are preferred if you think any user of your package will ever want to test for specific errors. They allow things like:
err := somepkg.Function()
if err == somepkg.ErrSomethingBad {
// …
}
// or for an error type, something like:
if e, ok := err.(somepkg.SomeError); ok && e.ExtraInfo > 42 {
// use the fields/methods of `e` if needed
}
For Go 1.13 or later, the above can instead be written as:
err := somepkg.Function()
if errors.Is(err, somepkg.ErrSomethingBad) {
// …
}
// or for an error type, something like:
var someErr somepkg.SomeError
if errors.As(err, &someErr) && someErr.ExtraInfo > 42 {
// use the fields/methods of `someErr` if needed
}
the difference is that errors will be unwrapped as needed.
The fifth way (which is just an extension of the second) allows checking the error for behaviour/type like so (or using Go 1.13's errors.As
):
if e, ok := err.(net.Error); ok && e.Timeout() {
// it's a timeout, sleep and retry
}
The problem with the third way is it leaves no sane way for a user of the package to test for it. (Testing the contents of the string returned by err.Error()
isn't a great idea).
However, it's fine for the errors that you don't ever expect anyone to want to test for.
Further reading:
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