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

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

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

Practice

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

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

建立一個新的類型app

type ErrNegativeSqrt float64
複製代碼

併爲其實現函數

func (e ErrNegativeSqrt) Error() string
複製代碼

方法使其擁有 error 值,經過 ErrNegativeSqrt(-2).Error() 調用該方法應返回 "cannot Sqrt negative number: -2"ui

注意:Error 方法內調用 fmt.Sprint(e) 會讓程序陷入死循環。能夠經過先轉換 e 來避免這個問題:fmt.Sprint(float64(e))。這是爲何呢?spa

修改 Sqrt 函數,使其接受一個負數時,返回 ErrNegativeSqrt 值。code

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上看到這塊詳細的源碼 github.com/golang/go/b…

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

相關文章
相關標籤/搜索