為什麼 Go 中有的自定義 error 會導致記憶體溢位

KevinYan發表於2019-11-09

分享一個在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/2ed57a8c...

這個練習感覺還是給開發者提示了一個非常隱蔽的坑,感興趣的可以去go tour上的這個練習題自己試驗一下。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
公眾號:網管叨bi叨 | Golang、Laravel、Docker、K8s等學習經驗分享

相關文章