DEV Community

Robin Moffatt
Robin Moffatt

Posted on • Originally published at rmoff.net on

Learning Golang (some rough notes) - S01E06 - Errors

šŸ‘‰ A Tour of Go : Exercise: Errors

Like Interfaces, the Tour didnā€™t really do it for me on Errors either. Too absract, and not enough explanation of the code examples for my liking. It also doesnā€™t cover the errors package which other tutorial do. Iā€™m not clear if thatā€™s because the errors package isnā€™t used much, or the Tour focusses only on teaching the raw basics.

Iā€™m quickly learning to head to gobyexample.com each time for more reference on things that arenā€™t making sense (along with https://www.calhoun.io/ too). The errors page on GoByExample is a good one, and I like how it links through to the Go Playground with each example. The Go Blogā€™s Error handling and Go is also a good reference, and this blog has some recent updates for Go 1.13.

Once Iā€™d gone through the additional links the errors exercise was OK to figure out:

package main

import (
    "fmt"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

func Sqrt(x float64) (float64, error) {
    fmt.Printf("\n--\nSqrt called with value: %v\n", x)
    if x < 0 {
        fmt.Printf("\t%v is less than zero. Returning with error.\n", x)
        return 0, ErrNegativeSqrt(x)
    }
    guess := 1.0
    limit := 10
    for i := 0; i < limit; i++ {
        adj := (guess*guess - x) / (2 * guess)
        if result := guess * guess; result == x {
            fmt.Printf("\tāœ…Guess %d is correct:\t%g\n", i, guess)
            return guess, nil
        } else if result > x {
            fmt.Printf("\tšŸ”ŗGuess %d is too high:\t%g\n", i, guess)
            guess -= adj
        } else {
            fmt.Printf("\tšŸ”»Guess %d is too low:\t%g\n", i, guess)
            guess -= adj
        }
    }
    return guess, nil

}

func main() {
    for _, x := range []float64{-9, 9} {
        if result, ok := Sqrt(x); ok == nil {
            fmt.Printf("-> result: %v\n", result)
        } else {
            fmt.Printf("** ERROR %v",ok.Error())
        }
    }
}

--
Sqrt called with value: -9
    -9 is less than zero. Returning with error.
** ERROR cannot Sqrt negative number: -9
--
Sqrt called with value: 9
    šŸ”»Guess 0 is too low: 1
    šŸ”ŗGuess 1 is too high:    5
    šŸ”ŗGuess 2 is too high:    3.4
    šŸ”ŗGuess 3 is too high:    3.023529411764706
    šŸ”ŗGuess 4 is too high:    3.00009155413138
    šŸ”ŗGuess 5 is too high:    3.000000001396984
    āœ…Guess 6 is correct:  3
-> result: 3
Enter fullscreen mode Exit fullscreen mode

Try it out: https://play.golang.org/p/mLa5RqwYckb

This bit had me puzzled:

Note: A call to fmt.Sprint(e) inside the Error method will send the program into an infinite loop. You can avoid this by converting e first: fmt.Sprint(float64(e)). Why?

If I changed it to

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("cannot Sqrt negative number: %v",e)
}
Enter fullscreen mode Exit fullscreen mode

then running it in the Tour window failed (as expected)

--
Sqrt called with value: -9
    -9 is less than zero. Returning with error.

Program exited: status 2.
Enter fullscreen mode Exit fullscreen mode

Running it in VSCode gave another error:

runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
Enter fullscreen mode Exit fullscreen mode

and sticking a print debug into the function shows that itā€™s recursively called:

func (e ErrNegativeSqrt) Error() string {
    fmt.Println("ErrNegativeSqrt.Error")
    return fmt.Sprintf("cannot Sqrt negative number: %v",e)
}

--
Sqrt called with value: -9
    -9 is less than zero. Returning with error.
ErrNegativeSqrt.Error
ErrNegativeSqrt.Error
ErrNegativeSqrt.Error
ErrNegativeSqrt.Error
ErrNegativeSqrt.Error
ErrNegativeSqrt.Error
[ā€¦]
Enter fullscreen mode Exit fullscreen mode

Butā€¦ I donā€™t understand why. StackOverflow turns up this explanation

fmt.Sprint(e) will call e.Error() to convert the value e to a string. If the Error() method calls fmt.Sprint(e), then the program recurses until out of memory.

You can break the recursion by converting the e to a value without a String or Error method.

Thus e is converted to float64:

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}
Enter fullscreen mode Exit fullscreen mode

This comment offers a neat alternative too:

Isnā€™t it enough to convert e to a type which may have a String/Error method that doesnā€™t recurse infinitely?

And hence specifying a different verb works:

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("cannot Sqrt negative number: %f",e)
}
Enter fullscreen mode Exit fullscreen mode
--
Sqrt called with value: -9
    -9 is less than zero. Returning with error.
** ERROR cannot Sqrt negative number: -9.000000
Enter fullscreen mode Exit fullscreen mode

Latest comments (2)

Collapse
 
wagslane profile image
Lane Wagner

What the hell?

Collapse
 
wagslane profile image
Lane Wagner

Hey Robin! Love the articles! I just finished building a "tour of go" style course. It uses web assembly to run go code directly in the browser and walks you through coding assignments.

I would love to get your feedback! If you want to try it out you can sign in at classroom.qvault.io

DM me and I'll unlock the course for you so you don't have to pay.