为什么Go的自定义error有时候会内存溢出

2019年10月13日 170点热度 0人点赞 0条评论


点击上方蓝色“Go语言中文网”关注我们,设个星标,每天学习 Go 语言


分享一个在go tour上看到的练习题,练习里要求用户自己定义一个错误类型,实现 error接口,函数在参数不满足条件的时候返回自定义的错误类型的值。练习中特别提示用户不要在实现的 Error方法里直接使用 fmt.Sprint(e)以避免造成程序内存溢出。

下面贴一下具体的练习题

Practice

从之前的练习中复制 Sqrt 函数,修改它使其返回 error 值。

Sqrt 接受到一个负数时,应当返回一个非 nil 的错误值。复数同样也不被支持。

创建一个新的类型

  1. type ErrNegativeSqrt float64

并为其实现

  1. 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函数的功能实现。

  1. package main


  2. import (

  3. "fmt"

  4. )


  5. type ErrNegativeSqrt float64


  6. func (e ErrNegativeSqrt) Error() string {

  7. // 这里直接使用e值会内存溢出

  8. return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))

  9. }


  10. func Sqrt(x float64) (float64, error) {

  11. if x < 0 {

  12. err := ErrNegativeSqrt(x)

  13. return 0, err

  14. }

  15. return 0, nil

  16. }


  17. func main() {

  18. fmt.Println(Sqrt(2))

  19. fmt.Println(Sqrt(-2))

  20. }

接下来探究一下为什么在练习中把值 e先转换为float64类型后程序就不会再内存溢出。

fmt.Sprint(e)将调用 e.Error()e转换为字符串。如果 Error()方法调用 fmt.Sprint(e),则程序将递归直到内存溢出。可以通过将 e转换成一个非错误类型(未实现Error接口)的值来避免这种情况。

实际上在 Error方法中把 error值直接传递给 fmt包中Print相关的函数都会导致无限循环。原因可以在fmt包的源码中找到。

  1. switch verb {

  2. case 'v', 's', 'x', 'X', 'q':

  3. // Is it an error or Stringer?

  4. // The duplication in the bodies is necessary:

  5. // setting wasString and handled, and deferring catchPanic,

  6. // must happen before calling the method.

  7. switch v := p.field.(type) {

  8. case error:

  9. wasString = false

  10. handled = true

  11. defer p.catchPanic(p.field, verb)

  12. // 这里调用了Error方法

  13. p.printField(v.Error(), verb, plus, false, depth)

  14. return

`

通过链接可以在Github上看到这块详细的源码 https://github.com/golang/go/blob/2ed57a8cd86cec36b8370fb16d450e5a29a9375f/src/pkg/fmt/print.go#L639

这个练习感觉还是给开发者提示了一个非常隐蔽的坑,感兴趣的可以通过阅读原文的链接访问到go tour上的这个练习题自己试验一下。

喜欢本文的朋友,欢迎关注“Go语言中文网”:

图片

28350为什么Go的自定义error有时候会内存溢出

这个人很懒,什么都没留下

文章评论