deferのタイミングで関数の戻り値を変えたい場合には、named return使うしかないのかも?

はじめに

例えば、以下の様な状況。

  • deferで何らかの後片付けをする。
  • この後片付けのタイミングでerrorが発生。

この時、deferの中の処理で発生したerrorを戻り値として返したい。このような事をしようとした場合にnamed returnを使うしかなかったという話。

sample

f()の後片付けの最中のcleanup()でerrorが発生する場合。

func f()(string, error) {
    s, err := g()
    defer cleanup(s)
    return s, err
}

func g()(string, error) {
    return "ok", nil
}

func cleanup(s string) error {
    return fmt.Errorf("hmm")
}

もちろんこの時の出力結果は以下の様になる。

fmt.Println(f())
// ok, <nil>

deferの中で値を書き換えてもダメ

以下のように書き換えても、やっぱり戻り値はnilになってしまう。

func f()(string, error) {
    s, err := g()
    defer func(){
        if cerr := cleanup(s); err == nil && cerr != nil {
            err = cerr
        }
    }()
    return s, err
}

出力結果

fmt.Println(f())
// ok, <nil>

named returnを使うと大丈夫

named returnは個人的には好きじゃないのでなるべくなら避けたいものだったのだけれど。今回に限っては必要性を認めざるを得ない感じ。

func f()(s string, err error) {
    s, err = g()
    defer func(){
        if cerr := cleanup(s); err == nil && cerr != nil {
            err = cerr
        }
    }()
    return s, err // returnだけでもok
}

今度は発生したerror(hmm)を返す。

fmt.Println(f())
// ok hmm

ところで以下の様に書いてもcompile通っちゃうのが嫌な感じ。

あんまりないことではあるけれど、g()を呼び忘れたような状況。

func f()(s string, err error) {
    // s, err = g()
    defer func(){
        if cerr := cleanup(s); err == nil && cerr != nil {
            err = cerr
        }
    }()
    return s, err
}

zero値で初期化されちゃってるので。

fmt.Println(f())
//  hmm

参考