在Error方法中,由fmt.Sprint(e)产生的无限循环。

huangapple go评论73阅读模式
英文:

An infinite loop produced by fmt.Sprint(e) inside the Error method

问题

根据fortyforty对这个问题的回答:

> fmt.Sprint(e)会调用e.Error()将值e转换为字符串。如果Error()方法调用fmt.Sprint(e),那么程序会递归调用直到内存耗尽。
>
> 你可以通过将e转换为没有StringError方法的值来打破递归。

这对我来说仍然很困惑。为什么fmt.Sprint(e)调用e.Error()而不是String()?我尝试使用Stringer接口,这是我的代码:

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))
}
英文:

According to fortyforty's reply to this question:

> 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.

This is still confusing to me. Why does fmt.Sprint(e) call e.Error() instead of String()? I tried using the Stringer interface, this is my code:

package main

import (
  &quot;fmt&quot;
  &quot;math&quot;
)

type NegativeSqrt float64

func (e NegativeSqrt) Error() string {
  fmt.Printf(&quot;.&quot;)
  return fmt.Sprint(e)
}

func (e NegativeSqrt) String() string {
  return fmt.Sprintf(&quot;%f&quot;, e)
}

func Sqrt(x float64) (float64, error) {
  if x &lt; 0 {
    return 0, NegativeSqrt(x)
  }
  return math.Sqrt(x), nil
}

func main() {
  fmt.Println(Sqrt(2))
  fmt.Println(Sqrt(-2))
}

答案1

得分: 14

似乎直接解释了fmt包的源代码:

// 是否为错误或Stringer?
// 在函数体中的重复是必要的:
// 在调用方法之前,设置handled和defer catchPanic
// 必须发生。

然后调用Error()String()

这意味着首先调用error.Error()生成字符串,然后再次处理并打印为字符串。

这里是否error具有String方法并不重要。问题是为什么NegativeSqrt使用一种方法而不是另一种方法进行打印。类型NegativeSqrt同时实现了fmt.Stringererror接口,所以由fmt包的实现决定应该使用哪个接口来从NegativeSqrt获取string(因为fmt.Sprint通过interface{}接收其参数)。

为了说明这一点,请考虑以下示例:

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's stringer")
	case error:
		fmt.Println("It's error")
	}
}

func check2(val interface{}) {
	switch val.(type) {
	case error:
		fmt.Println("It's error")
	case fmt.Stringer:
		fmt.Println("It's stringer")
	}
}

func main() {
	var v NegativeSqrt
	check(v)
	check2(v)
}

执行结果为:

% go run a.go
It's stringer
It's error

这是因为在Go中,类型切换的行为就像普通的切换一样,所以case的顺序很重要

英文:

It seems it's explained directly is source of 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() is called.

What it means is that first error.Error() is called to produce string, which is than once again processed and is printed as string.

Whether error has method String is irrelevant here. The question is why NegativeSqrt is printed with one method and not the other. Type NegativeSqrt implements both fmt.Stringer and error interfaces so it's up to the implementation of fmt package which of interfaces should be used to get string from NegativeSqrt (since fmt.Sprint takes its parameters by interface{}).

To illustrate this consider this example:

package main

import (
	&quot;fmt&quot;
)

type NegativeSqrt float64

func (e NegativeSqrt) Error() string {
	return &quot;&quot;
}

func (e NegativeSqrt) String() string {
	return &quot;&quot;
}

func check(val interface{}) {
	switch val.(type) {
	case fmt.Stringer:
		fmt.Println(&quot;It&#39;s stringer&quot;)
	case error:
		fmt.Println(&quot;It&#39;s error&quot;)
	}
}

func check2(val interface{}) {
	switch val.(type) {
	case error:
		fmt.Println(&quot;It&#39;s error&quot;)
	case fmt.Stringer:
		fmt.Println(&quot;It&#39;s stringer&quot;)
	}
}

func main() {
	var v NegativeSqrt
	check(v)
	check2(v)
}

Executing this gives:

% go run a.go
It&#39;s stringer
It&#39;s error

This is because in Go type switch behaves just like normal switch, so order of cases matters.

答案2

得分: 5

因为类型是error,而error的接口是:

  type error interface{
     Error() string
  }

每个error都必须有一个Error() string方法,但不一定要有一个String() string方法。这就是为什么首先要检查Error()方法的逻辑。

英文:

Because the type is error and the interface of error is

  type error interface{
     Error() string
  }

every error must have an Error() string method but does not have to have a String() string method. That why is logic to check first the Error() method.

答案3

得分: 1

让我详细解释一下tumdum的发现,以便更清楚地理解。

我将从一个调用跳到另一个调用,以展示我们如何进入循环。

我们从练习的以下代码开始:

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

这将带我们到fmt/print.go的第237行:

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

在该函数内部,我们下一个跳转发生在第239行:

p.doPrint(a, false, false)

我们到达第1261行:

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

在该函数内部,我们将通过第1273行的error参数进行跳转:

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

我们到达第738行的一个庞大的核心函数:

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

在其中,你可以看到一个大的switch case语句。由于error被认为是一个非平凡的类型,它进入了default部分。

这将带我们到第806行,调用handleMethods()

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

我们到达第688行:

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

在该函数的内部,在第724行,调用了Error(),这样循环就完成了:

p.printArg(v.Error(), verb, depth)
英文:

Let me expand tumdum's finding for better clarity.

I'll jump from a call to call to show how we go into loop.

We start from the exercise's

func (e NegativeSqrt) Error() string {
  fmt.Printf(&quot;.&quot;)
  return fmt.Sprint(e)
}

Which delivers us to a line 237 of the fmt/print.go:

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

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

p.doPrint(a, false, false)

We arrive to line 1261:

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

Inside that function, we'll jump, with our error argument, through line 1273:

prevString = p.printArg(arg, &#39;v&#39;, 0)

We arrive at a huge core monster function at line 738:

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

Inside that, you can see a large switch case switch. error goes in the default section as it is deemed to be a non-trivial type.

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

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

We arrive at line 688:

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

Inside of that function, on line 724, call to Error() happens, which completes the loop:

p.printArg(v.Error(), verb, depth)

huangapple
  • 本文由 发表于 2015年1月14日 02:14:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/27928744.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定