Go 在错误方法中由fmt.Sprint(e)产生的无限循环

Go 在错误方法中由fmt.Sprint(e)产生的无限循环,go,Go,根据fortyforty对以下内容的回复: fmt.Sprint(e)将调用e.Error() 字符串。如果Error()方法调用fmt.Sprint(e),则 程序递归,直到内存不足 您可以通过将e转换为不带 字符串或错误方法 这仍然让我感到困惑。为什么fmt.Sprint(e)调用e.Error()而不是String()?我尝试使用Stringer接口,这是我的代码: package main import ( "fmt" "math" ) type NegativeSqrt f

根据fortyforty对以下内容的回复:

fmt.Sprint(e)
将调用
e.Error()
字符串
。如果
Error()
方法调用
fmt.Sprint(e)
,则 程序递归,直到内存不足

您可以通过将
e
转换为不带
字符串
错误
方法

这仍然让我感到困惑。为什么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))
}
主程序包
进口(
“fmt”
“数学”
)
类型NegativeSqrt float64
func(e NegativeSqrt)错误()字符串{
格式打印F(“.”)
返回fmt.Sprint(e)
}
func(e NegativeSqrt)String()字符串{
返回fmt.Sprintf(“%f”,e)
}
func Sqrt(x float64)(float64,错误){
如果x<0{
返回0,负数qrt(x)
}
返回math.Sqrt(x),无
}
func main(){
格式打印(Sqrt(2))
格式打印(Sqrt(-2))
}
似乎解释了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
是否有方法
String
在这里是无关的。问题是为什么
负片qrt
是用一种方法而不是另一种方法打印的。Type
NegativeSqrt
实现了
fmt.Stringer
error
接口,因此取决于
fmt
包的实现,应该使用哪个接口从
NegativeSqrt
获取
字符串
(因为它的参数是通过
接口{}

为了说明这一点,请考虑这个例子:

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

这是因为in-Go类型开关的行为与普通开关类似,因此。

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

  type error interface{
     Error() string
  }

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

为了更清晰,让我扩展一下tumdum的发现

我将从一个呼叫跳到另一个呼叫,展示我们如何进入循环

我们从练习的开始

func (e NegativeSqrt) Error() string {
  fmt.Printf(".")
  return fmt.Sprint(e)
}
这将我们带到第237行:

在函数内部,我们的下一个跳转位于第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) {
在里面,你可以看到一个大的
开关盒
开关<代码>错误
出现在
默认
部分,因为它被认为是一种非平凡类型

通过调用
handleMethods()
,我们将看到第806行:

我们到达688号线:

func (p *pp) handleMethods(verb rune, depth int) (handled bool) {
在该函数内部,第724行调用
Error()
,完成循环:

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

Tomasz Kłak的回答也很精彩,但这一个解释了为什么它是以这种方式实现的。我不认为这个问题是关于主题/正确的。在你的“编辑”之后,这个回答现在解释了为什么我们有这种行为,谢谢。编辑:我修复了链接。给定的链接不起作用。通过指向提交而不是主提交来提供链接,它可以更改。
func (p *pp) handleMethods(verb rune, depth int) (handled bool) {
p.printArg(v.Error(), verb, depth)