Errors
Errors are treated as values in Go. The error type is a built-in interface. An error variable represents any value that can describe itself as a string.
type error interface {
Error() string
}
Functions often return an error value, and calling code should handle errors by testing whether the error equals nil.
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)
The most commonly-used error implementation is the errors package’s unexported errorString type.
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
You can construct one of these values with the errors.New function. It takes a string that it converts to an errors.errorString and returns as an error value.
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
Here’s how you might use errors.New:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// implementation
}
A caller passing a negative argument to Sqrt receives a non-nil error value (whose concrete representation is an errors.errorString value). The caller can access the error string (“math: square root of…”) by calling the error’s Error method, or by just printing it:
f, err := Sqrt(-1)
if err != nil {
fmt.Println(err)
}
It’s possible to use custom types as errors by implementing the Error() method on them.
package main
import (
"fmt"
)
type MyError struct {
Msg string
When time.Time
}
func (e *MyError) Error() string {
return fmt.Sprintf("Oops! %s at %v", e.Msg,e.When)
}
func run() error {
return &MyError{
Msg: "It didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
Output:
Oops! It didn't work at 0001-01-01 00:00:00 +0000 UTC
- Question: How does the above code works? We are passing the error interface to fmt.Println(err), But how does it print the string “Oops! It didn’t work at 0001-01-01 00:00:00 +0000 UTC” from fmt.Println(err)?
The fmt package formats an error value by calling its Error() string method.
To find out the answer, you have to go through the source code of package “fmt”. Here is the code snippet from the package “fmt” that actually implements this functionality
switch v := p.arg.(type) {
case error:
handled = true
defer p.catchPanic(p.arg, verb, "Error")
p.fmtString(v.Error(), verb)
return
case Stringer:
handled = true
defer p.catchPanic(p.arg, verb, "String")
p.fmtString(v.String(), verb)
return
}
In our code, argument of Println() = err which is of type *MyError that implements the error interface. Hence it calls the method Error() of the *MyError object.
So, in this case
fmt.Println(err)
is equivalent to
fmt.Println(err.Error())
Example:
package main
import (
"fmt"
"time"
)
type MyError struct {
Msg string
When time.Time
}
func (e *MyError) Error() string {
return fmt.Sprintf("Oops! %s at %v", e.Msg,e.When)
}
func run() error {
return &MyError{
Msg: "It didn't work",
}
}
func main() {
e := &MyError{
Msg:"It didn't work",
}
if err := run(); err != nil {
fmt.Println(err.Error())
fmt.Println(err)
fmt.Println(e.Error())
}
}
Output:
Oops! It didn't work at 0001-01-01 00:00:00 +0000 UTC
Oops! It didn't work at 0001-01-01 00:00:00 +0000 UTC
Oops! It didn't work at 0001-01-01 00:00:00 +0000 UTC