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.Error()生成字符串,然后再次处理并打印为字符串。
error
是否有方法String
与这里无关。问题是为什么 NegativeSqrt
是用一种方法而不是另一种方法打印的。类型 NegativeSqrt
实现了 fmt.Stringer
和 error
接口,所以这取决于 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.
因为类型是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)
}
将我们带到 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)
根据 fortyforty 对 this question 的回复:
fmt.Sprint(e)
will calle.Error()
to convert the valuee
to astring
. If theError()
method callsfmt.Sprint(e)
, then the program recurses until out of memory.You can break the recursion by converting the
e
to a value without aString
orError
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.Error()生成字符串,然后再次处理并打印为字符串。
error
是否有方法String
与这里无关。问题是为什么 NegativeSqrt
是用一种方法而不是另一种方法打印的。类型 NegativeSqrt
实现了 fmt.Stringer
和 error
接口,所以这取决于 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.
因为类型是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)
}
将我们带到 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()
:
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)