In Go, there’s no special mechanism for directly binding methods to error types. Errors in Go are simply values that fulfill the error interface, which has a single method, Error() string. Therefore, any type that implements this method can be used as an error in Go.
If you are new to the world of Go, I recommend the following previous articles:
The first thing we need to know is that in Go, the error value returned by a function should always be the last one, for example:
func f1(arg int) (int, error) {
if arg == 42 {
return -1, errors.New("can't work with 42")
}
return arg + 3, nil
}
We can easily create custom errors by simply implementing the error interface, which has a single method, Error() string. Therefore, we create a structure with the required fields. This structure will be associated with the Error() function due to
Go’s methods
. Next, in the function f2
, we create an object of type argError, passing the expected values ββfor the structure as parameters:
type argError struct {
arg int
prob string
}
func (e *argError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.prob)
}
func f2(arg int) (int, error) {
if arg == 42 {
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}
If we don’t want to display the error exactly as it comes from our Error function, we can capture the error and access each of the data fields in the structure. In this case, the error is captured in the variable e
, and then we dump the fields into the variable fieldsStructure, which has the fields defined in the argError structure. The second return parameter indicates whether the dumping was successful or not. In the case of success, we print the data:
_, e := f2(42)
if fieldsStructure, ok := e.(*argError); ok {
fmt.Println(fieldsStructure.arg)
fmt.Println(fieldsStructure.prob)
}
Let’s see the complete example:
package main
import (
"errors"
"fmt"
)
func f1(arg int) (int, error) {
if arg == 42 {
return -1, errors.New("can't work with 42")
}
return arg + 3, nil
}
type argError struct {
arg int
prob string
}
func (e *argError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.prob)
}
func f2(arg int) (int, error) {
if arg == 42 {
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}
func main() {
for _, i := range []int{7, 42} {
if r, e := f1(i); e != nil {
fmt.Println("f1 failed:", e)
} else {
fmt.Println("f1 worked:", r)
}
}
for _, i := range []int{7, 42} {
if r, e := f2(i); e != nil {
fmt.Println("f2 failed:", e)
} else {
fmt.Println("f2 worked:", r)
}
}
_, e := f2(42)
if fieldsStructure, ok := e.(*argError); ok {
fmt.Println(fieldsStructure.arg)
fmt.Println(fieldsStructure.prob)
}
}
We execute the code:
f1 worked: 10
f1 failed: can't work with 42
f2 worked: 10
f2 failed: 42 - can't work with it
42
can't work with it