爲何Go中有的自定義error會致使內存溢出

分享一個在go tour上看到的練習題,練習裏要求用戶本身定義一個錯誤類型,實現error接口,函數在參數不知足條件的時候返回自定義的錯誤類型的值。練習中特別提示用戶不要在實現的Error方法裏直接使用fmt.Sprint(e)以免形成程序內存溢出。git

下面貼一下具體的練習題github

Practice

從以前的練習中複製 Sqrt 函數,修改它使其返回 error 值。golang

Sqrt 接受到一個負數時,應當返回一個非 nil 的錯誤值。複數一樣也不被支持。app

建立一個新的類型函數

type ErrNegativeSqrt float64

併爲其實現code

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/...

這個練習感受仍是給開發者提示了一個很是隱蔽的坑,感興趣的能夠去go tour上的這個練習題本身試驗一下。

相關文章
相關標籤/搜索