The infinite loop created by fmt.Sprint (e) inside the Error method

In accordance with his co-author's answer to this question :

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

You can break recursion by converting e to a value without string or Error .

It still confuses me. Why does fmt.Sprint (e) raise e.Error () instead of String ()? I tried using the Stringer interface, this is my code:

 package main import ( "fmt" "math" ) type NegativeSqrt float64 func (e NegativeSqrt) Error() string { fmt.Printf(".") return fmt.Sprint(e) } func (e NegativeSqrt) String() string { return fmt.Sprintf("%f", e) } func Sqrt(x float64) (float64, error) { if x < 0 { return 0, NegativeSqrt(x) } return math.Sqrt(x), nil } func main() { fmt.Println(Sqrt(2)) fmt.Println(Sqrt(-2)) } 
+7
go
source share
3 answers

This seems to be explained directly is the source of the fmt package:

 // Is it an error or Stringer? // The duplication in the bodies is necessary: // setting handled and deferring catchPanic // must happen before calling the method. 

And than Error () or String () .

This means that the first error.Error () is called to create a string that is more processed and printed as a string.

Is there an error String method here. The question is why NegativeSqrt is printed in one way and not the other. The type NegativeSqrt implements the fmt.Stringer and error interfaces, therefore, before implementing the fmt package, which interface should be used to get a String from NegativeSqrt (since fmt.Sprint accepts its interface{} parameters).

To illustrate this, consider the following example:

 package main import ( "fmt" ) type NegativeSqrt float64 func (e NegativeSqrt) Error() string { return "" } func (e NegativeSqrt) String() string { return "" } func check(val interface{}) { switch val.(type) { case fmt.Stringer: fmt.Println("It stringer") case error: fmt.Println("It error") } } func check2(val interface{}) { switch val.(type) { case error: fmt.Println("It error") case fmt.Stringer: fmt.Println("It stringer") } } func main() { var v NegativeSqrt check(v) check2(v) } 

Doing this gives:

 % go run a.go It stringer It error 

This is because a Go-type switch behaves exactly the same as a regular switch, so the order of things matters .

+9
source share

Since the type is error , and the error interface is

  type error interface{ Error() string } 

each error must have an Error() string method, but must not have a String() string method. This is why logic first checks the Error() method.

+1
source share

Let me expand tumor detection for better clarity.

I will go from call to call to show how we enter the loop.

We start with the exercise.

 func (e NegativeSqrt) Error() string { fmt.Printf(".") return fmt.Sprint(e) } 

Which brings us to line 237 fmt / print.go :

 func Sprint(a ...interface{}) string 

Inside the function, our next jump is on line 239:

 p.doPrint(a, false, false) 

We come to line 1261:

 func (p *pp) doPrint(a []interface{}, addspace, addnewline bool) { 

Inside this function, we will jump with our error argument on line 1273:

 prevString = p.printArg(arg, 'v', 0) 

We come to the huge kernel monster function on line 738:

 func (p *pp) printArg(arg interface{}, verb rune, depth int) (wasString bool) { 

Inside this you can see a large switch case . error goes to the default section, as it is considered a non-trivial type.

Which brings us to line 806 with the handleMethods() call:

 if handled := p.handleMethods(verb, depth); handled { 

We come to line 688:

 func (p *pp) handleMethods(verb rune, depth int) (handled bool) { 

Inside this function, on line 724, the Error() call is made, which completes the loop:

 p.printArg(v.Error(), verb, depth) 
0
source share

All Articles