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

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

根据 fortyforty 对 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.

这仍然让我感到困惑。为什么 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))
}

好像已经解释了 directly 是 fmt 包的来源:

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

然后调用 Error() or String()

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

error是否有方法String与这里无关。问题是为什么 NegativeSqrt 是用一种方法而不是另一种方法打印的。类型 NegativeSqrt 实现了 fmt.Stringererror 接口,所以这取决于 fmt 包的实现应该使用哪个接口从 [=15] 获取 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 中类型 switch 的行为就像普通的 switch,所以 order of cases matters.

因为类型是errorerror的接口是

  type error interface{
     Error() string
  }

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

为了更清楚起见,让我扩展一下 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) {

在该函数中,我们将使用我们的 error 参数跳转至第 1273 行:

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

我们在第 738 行到达了一个巨大的核心怪物函数:

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

在那里面,你可以看到一个很大的 switch case 开关。 error 进入 default 部分,因为它被认为是一个非常重要的类型。

通过调用 handleMethods():

将我们传送到第 806 行
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)