4

The program below prints "2.5"

package main

import (
    "fmt"
)

type myFloat64 float64

// func (f myFloat64) Error() string {
//     return fmt.Sprintf("Error with %v", f)
// }

func main() {
    var a myFloat64 = myFloat64(2.5)
    fmt.Printf("%v\n", a)
}

Interestingly, when I uncomment the Error() method, the program no longer prints "2.5"

Question: Why does adding an Error() method change program behavior? Pointers to the Go language specification explaining this behavior would be much appreciated.

Jorge
  • 109
  • 5
  • This is how the `fmt` package is implemented. If the printed value implements `error`, its `Error()` method is called. – icza Dec 29 '21 at 07:20
  • 1
    I started on an answer that would be at least partly redundant (hence the closed question) but look at [this version on the Go playground](https://go.dev/play/). Note that I changed the `fmt.Sprintf` to use `%f`; if you leave `%v` in here and run this, you'll see the infinite recursion. – torek Dec 29 '21 at 07:21
  • 1
    Because %v calls Error which again uses %v to format the value which calls Error whic hagain formats the value with %v which again calls Error .... You got a infinite loop which produces a SO. – Volker Dec 29 '21 at 07:21
  • 2
    I kinda think this question should be opened for other people asking the same question. The answer, OP, is because when you add the Error method to your float, you're creating an error type. You can see the spec here: https://go.dev/ref/spec#Errors Then, when you call Printf, the code calls that method during printing, defined here: https://cs.opensource.google/go/go/+/refs/tags/go1.17.5:src/fmt/print.go;l=620;drc=refs%2Ftags%2Fgo1.17.5 – 425nesp Dec 29 '21 at 07:24
  • You can avoid the infinite recursion by converting f first: `return fmt.Sprintf("Error with %v", float64(f))` – Jorge Dec 29 '21 at 07:34
  • 2
    @Jorge, yes, your trick works, but the main problem is %v. This is a handy tool to format a thing you do not know what it is. But if you know what it is: Use an appropriate verb for your type. – Volker Dec 29 '21 at 07:40
  • 1
    I wish the Go playground told users about stack overflows. Debugging would've been easier. – Jorge Dec 29 '21 at 08:05

1 Answers1

5

myFloat64 implements the error interface:

type error interface {
  Error() string
}

fmt.Println() will consider a as an error value, and print the error message by calling a.Error(), which executes fmt.Sprintf("Error with %v", f). But Sprintf behaves just like Println, it also considers f as an error value and calls Error(). This recursion goes infinitely and causes stack overflow.

Allan Wang
  • 71
  • 2