点击上方蓝色“Go语言中文网”关注我们,设个星标,每天学习 Go 语言
分享一个在go tour上看到的练习题,练习里要求用户自己定义一个错误类型,实现 error
接口,函数在参数不满足条件的时候返回自定义的错误类型的值。练习中特别提示用户不要在实现的 Error
方法里直接使用 fmt.Sprint(e)
以避免造成程序内存溢出。
下面贴一下具体的练习题
Practice
从之前的练习中复制 Sqrt
函数,修改它使其返回 error
值。
Sqrt
接受到一个负数时,应当返回一个非 nil 的错误值。复数同样也不被支持。
创建一个新的类型
type ErrNegativeSqrt float64
并为其实现
func (e ErrNegativeSqrt) Error() string
方法使其拥有 error
值,通过 ErrNegativeSqrt(-2).Error()
调用该方法应返回 "cannot Sqrt negative number: -2"
。
注意: 在 Error
方法内调用 fmt.Sprint(e)
会让程序陷入死循环。可以通过先转换 e
来避免这个问题:fmt.Sprint(float64(e))
。这是为什么呢?
修改 Sqrt
函数,使其接受一个负数时,返回 ErrNegativeSqrt
值。
Solution
这里只为叙述返回error的情况,所以请忽略Sqrt函数的功能实现。
package main
import (
"fmt"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
// 这里直接使用e值会内存溢出
return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}
func Sqrt(x float64) (float64, error) {
if x < 0 {
err := ErrNegativeSqrt(x)
return 0, err
}
return 0, nil
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}
接下来探究一下为什么在练习中把值 e
先转换为float64类型后程序就不会再内存溢出。
fmt.Sprint(e)
将调用 e.Error()
将 e
转换为字符串。如果 Error()
方法调用 fmt.Sprint(e)
,则程序将递归直到内存溢出。可以通过将 e
转换成一个非错误类型(未实现Error接口)的值来避免这种情况。
实际上在 Error
方法中把 error
值直接传递给 fmt
包中Print相关的函数都会导致无限循环。原因可以在fmt包的源码中找到。
switch verb {
case 'v', 's', 'x', 'X', 'q':
// Is it an error or Stringer?
// The duplication in the bodies is necessary:
// setting wasString and handled, and deferring catchPanic,
// must happen before calling the method.
switch v := p.field.(type) {
case error:
wasString = false
handled = true
defer p.catchPanic(p.field, verb)
// 这里调用了Error方法
p.printField(v.Error(), verb, plus, false, depth)
return
`
通过链接可以在Github上看到这块详细的源码 https://github.com/golang/go/blob/2ed57a8cd86cec36b8370fb16d450e5a29a9375f/src/pkg/fmt/print.go#L639
这个练习感觉还是给开发者提示了一个非常隐蔽的坑,感兴趣的可以通过阅读原文的链接访问到go tour上的这个练习题自己试验一下。
喜欢本文的朋友,欢迎关注“Go语言中文网”:
文章评论