I was going through this article by Andrew Gerrand and the author mentions that since error is an interface
you can use arbitrary data structures as error values, to allow callers to inspect the details of the error.
and gives this example
type NegativeSqrtError float64
func (f NegativeSqrtError) Error() string {
return fmt.Sprintf("math: square root of negative number %g", float64(f))
}
but didn't really show how it is implemented, only discussing its possible use in type assertions. Although I imagined you could simply just return it with a value when the situation occurs as opposed to using fmt.Errorf as shown in the example below
package main
//trying to define a custom error type in Go
import "fmt"
type NegativeError float64
func (f NegativeError) Error() string {
return fmt.Sprintf("%v: temperature can't go below absolute zero", f)
}
func compute(a, b float64) (float64, error) {
var t float64
t = a - b
if t < 0 {
return t, NegativeError(t)
}
return t, nil
}
func main() {
fmt.Println(compute(4, 5))
}
But this doesn't work and gives rise to the error below
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc0200e13a0 stack=[0xc0200e0000, 0xc0400e0000]
fatal error: stack overflow
This seems to only be an issue when you implement the error interface's Error() method, but it works when you change the method name. (shown below)
package main
//trying to define a custom error type in Go
import "fmt"
type NegativeError float64
func (f NegativeError) Err() string {
return fmt.Sprintf("%v: temperature can't go below absolute zero", f)
}
func compute(a, b float64) (float64, string) {
var t float64
t = a - b
if t < 0 {
return t, NegativeError(t).Err()
}
return t, ""
}
func main() {
fmt.Println(compute(4, 5))
}
or when you implement the error interface on a struct type and not a float. (shown below)
package main
//trying to define a custom error type in Go
import (
"fmt"
)
type NegativeError struct {
value float64
}
func (f NegativeError) Error() string {
return fmt.Sprintf("%v: temperature can't go below absolute zero", f.value)
}
func compute(a, b float64) (float64, error) {
var t float64
t = a - b
if t < 0 {
return t, NegativeError{t}
}
return t, nil
}
func main() {
fmt.Println(compute(4, 5))
}
The obvious easy fix is to implement the interface on a struct type, but I would like to know if anyone else has experienced this particular error and how they handled it, or if this is the wrong way to go about it. Thanks.
As already mentioned in the comments, the
Error()method calls itself as it is used to format an error type to a string if you pass the error type itself tofmt.Sprintf.There are several ways to fix this:
fto the underlyingfloat64type and pass that intofmt.Sprintf:%vbut instead%f. The difference is that%vwill try to stringify the value and forerrortypes that means calling theError() stringmethod.%fwill treat thefas afloat64, which it is so it will work:From the solutions you've suggested, the solution using a struct is the most optimal approach IMO (apart from the 2 I mentioned above). It opens up the possibility to add more data to the error later, too. Especially error wrapping comes to mind which for me all my custom errors need to be able to do.
Error wrapping: The error can contain another error and has a method to
Unwrapthe original error (as defined by the standard library errors package). It is needed to be able to get to the original error for wrapped errors. e.g.fmt.Errorf("some error: %w", err)creates a new error witherrwrapped inside.