I'm working through A Tour of Go - Exercise: Errors. It holds my hand as I add error handling to a square root function.
Here is my solution:
package main
import (
"fmt"
"math"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
fmt.Sprint(float64(e))
return fmt.Sprintf("cannot Sqrt negative number: %g", float64(e))
}
func Sqrt(x float64) (float64, error) {
z := 1.0
margin := 0.00000000000001
for {
if x < 0 {
return 0, ErrNegativeSqrt(x)
}
previousZ := z
z = z - (z*z-x)/(2*z)
if math.Abs(previousZ-z) < margin {
fmt.Println(previousZ, "-", z, "=", previousZ-z)
break
}
}
fmt.Println("math.Sqrt:", math.Sqrt(x))
return z, nil
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}
I am having trouble understanding the line:
type ErrNegativeSqrt float64
I have two questions:
and
I'm a beginner at Go and I'd like to understand. Thank you so much.
I'm gonna answer your second question first: you add a method to your type so ErrNegativeSqrt implements the error interface, which means other code that does not know about the specifics of ErrNegativeSqrt can use it as any other error.
As the error interface only requires a method Error() with return type string, a common type to use is just a string with the Error method returning that string (defined in package "errors").
I don't know why it was decided to use float64 as the error type in this case, but the main advantage that I see is that if you had several functions that could return a similar error, you'd have to do the following:
func Method1() error{
...
return errors.New(fmt.Sprintf("cannot Sqrt negative number: %g", number)
}
func Method2() error{
...
return errors.New(fmt.Sprintf("cannot Sqrt negative number: %g", number2)
}
And repeat for any function that needs to return a similar thing. With the code that you posted, you only declare the number responsible for the error when you create it, and the method "Error()" returns the formated string for all.
func Method1() error{
...
ErrNegativeSqrt(number)
}
func Method2() error{
...
ErrNegativeSqrt(number)
}
One usually doesn't create custom error types, but this is a demonstration of how errors work, as well as some other Go concepts.
Why is the underlying type of ErrNegativeSqrt "float64" and not "error"?
This allows the float64
value in Sqrt
to be directly converted to an error. It also demonstrates how new types can have various underlying types, and methods can be attached to types other than structs. If you need to create your own type of error, use the underlying type that is most convenient for your use case.
Why create a type in the first place? Why do we create an error type and add a method to it instead of just having a free-standing function?
Sometimes an error needs to have more information or context than can be contained in the single string returned from errors.New
, or fmt.Errorf
. For example, the net
package uses a net.OpError
(which conforms to the net.Error
interface) to package information about addresses, whether it's a temporary error, or a caused by a timeout.
In most cases a simple string-based error will suffice.
Like any other languages, we create a type or object to make sense and group functionalities for our own sakes (computer doesn't care). In Go, it is common to create a custom type or a struct type to extend the functionalities of an object.
In this case, creating a type ErrNegativeSqrt
you can actually extend a float64
functionalities by adding methods. For instance, compare this two approaches:
func TimesTwo(val int) int {
return val * 2
}
with
type SuperChargedInt int
func (sc SuperChargedInt) TimesTwo() int {
return int(sc) * 2
}
Instead of passing an int
to a function, a SuperChargedInt
has the capability to act on itself. This becomes very handy when you tread into interfaces.
type MyInt interface {
TimesTwo() int
}
type IntA int
type IntB int
func (a IntA) TimesTwo() int {
return int(a) * 2
}
func (b IntB) TimesTwo() int {
return int(b) * (1 + 1)
}
func PrintMyInt(c MyInt) {
fmt.Println(c)
}
Type IntA
and IntB
, satisfying the interface MyInt
because they both implement the required methods, can be use anywhere in the code that requires MyInt
.
var a IntA = 34
var b IntB = 32
PrintMyInt(a)
PrintMyInt(b)
type SomeNum struct {
A MyInt
}
num := SomeNum{ A: a }
With the understanding of interfaces, now coming back to ErrNegativeSqrt
type, adding Error()
method to it make the type implements the error
interface (which has only one method Error()
to implement). Now you can use the type just like an error anywhere.
This is the duck-typing in Golang.
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