英文:
An infinite loop produced by fmt.Sprint(e) inside the Error method
问题
根据fortyforty对这个问题的回答:
> fmt.Sprint(e)
会调用e.Error()
将值e
转换为字符串。如果Error()
方法调用fmt.Sprint(e)
,那么程序会递归调用直到内存耗尽。
>
> 你可以通过将e
转换为没有String
或Error
方法的值来打破递归。
这对我来说仍然很困惑。为什么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 (
"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))
}
答案1
得分: 14
似乎直接解释了fmt包的源代码:
// 是否为错误或Stringer?
// 在函数体中的重复是必要的:
// 在调用方法之前,设置handled和defer catchPanic
// 必须发生。
这意味着首先调用error.Error()生成字符串,然后再次处理并打印为字符串。
这里是否error
具有String
方法并不重要。问题是为什么NegativeSqrt
使用一种方法而不是另一种方法进行打印。类型NegativeSqrt
同时实现了fmt.Stringer
和error
接口,所以由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 (
"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)
}
Executing this gives:
% go run a.go
It's stringer
It'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(".")
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, 'v', 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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论