73

I am going through "A Tour of Go" tutorial.

I would like to check the answer to this question:

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?


I believe this is because when the Sprint function is called, since the error is non-nil, the Error function() will again be called, and so forth, resulting in an infinite loop.

informatik01
  • 16,038
  • 10
  • 74
  • 104
pyrrhic
  • 1,769
  • 2
  • 15
  • 27

2 Answers2

94

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.

Dave C
  • 7,729
  • 4
  • 49
  • 65
  • 4
    Isn't it enough to convert e to a type which may have a String/Error method that doesn't recurse infinitely? – user_ Mar 28 '18 at 17:50
  • 3
    Yes, `fmt.Sprintf("cannot Sqrt negative number: %f", e)` works – laurelnaiad Feb 05 '20 at 19:35
  • 7
    But why does it try to call `e.Error()` to do the conversion? If I try to make it implement the Stringer interface, it doesn't try to call it. What makes it seek out to the `Error()` method? – hytromo Aug 10 '20 at 20:25
  • 9
    @hytromo In [fmt/print.go](https://golang.org/src/fmt/print.go?s=7053:7089), `Sprint` eventually leads to `handleMethods` with a verb of `v`. The [switch inside handleMethods](https://golang.org/src/fmt/print.go#L614) checks if the argument satisfies the error type before Stringer, resulting in the `Error()` method being called instead of `String()`. – getWeberForStackExchange Feb 07 '21 at 17:20
15

fmt.Sprint(e) will invoke the following piece of codes from "fmt/print.go"

switch verb {
    case 'v', 's', 'x', 'X', 'q':
        // Is it an error or Stringer?
        // The duplication in the bodies is necessary:
        // setting handled and deferring catchPanic
        // must happen before calling the method.
        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
        }
    }

As error case appears first, v.Error() will be executed. Endless loop here!

martin mo
  • 197
  • 1
  • 6